| 1 | //! JSON (RFC 8259): log record payloads, and later the API. |
| 2 | //! |
| 3 | //! Objects keep their key order, so encoding is deterministic. Integers are kept exact as i64; |
| 4 | //! other numbers are f64. |
| 5 | |
| 6 | use std::fmt::Write as _; |
| 7 | |
| 8 | #[derive(Clone, Debug, PartialEq)] |
| 9 | pub enum Value { |
| 10 | Null, |
| 11 | Bool(bool), |
| 12 | Int(i64), |
| 13 | Float(f64), |
| 14 | Str(String), |
| 15 | Array(Vec<Value>), |
| 16 | Object(Vec<(String, Value)>), |
| 17 | } |
| 18 | |
| 19 | impl Value { |
| 20 | pub fn get(&self, key: &str) -> Option<&Value> { |
| 21 | match self { |
| 22 | Value::Object(fields) => fields.iter().find(|(k, _)| k == key).map(|(_, v)| v), |
| 23 | _ => None, |
| 24 | } |
| 25 | } |
| 26 | pub fn as_str(&self) -> Option<&str> { |
| 27 | match self { |
| 28 | Value::Str(s) => Some(s), |
| 29 | _ => None, |
| 30 | } |
| 31 | } |
| 32 | pub fn as_i64(&self) -> Option<i64> { |
| 33 | match self { |
| 34 | Value::Int(n) => Some(*n), |
| 35 | _ => None, |
| 36 | } |
| 37 | } |
| 38 | pub fn as_array(&self) -> Option<&[Value]> { |
| 39 | match self { |
| 40 | Value::Array(a) => Some(a), |
| 41 | _ => None, |
| 42 | } |
| 43 | } |
| 44 | pub fn is_null(&self) -> bool { |
| 45 | matches!(self, Value::Null) |
| 46 | } |
| 47 | |
| 48 | pub fn encode(&self) -> String { |
| 49 | let mut out = String::new(); |
| 50 | self.write(&mut out); |
| 51 | out |
| 52 | } |
| 53 | |
| 54 | fn write(&self, out: &mut String) { |
| 55 | match self { |
| 56 | Value::Null => out.push_str("null"), |
| 57 | Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }), |
| 58 | Value::Int(n) => { |
| 59 | let _ = write!(out, "{n}"); |
| 60 | } |
| 61 | Value::Float(f) => { |
| 62 | if f.is_finite() { |
| 63 | let _ = write!(out, "{f:?}"); |
| 64 | } else { |
| 65 | out.push_str("null"); // JSON has no NaN or infinity |
| 66 | } |
| 67 | } |
| 68 | Value::Str(s) => write_str(s, out), |
| 69 | Value::Array(items) => { |
| 70 | out.push('['); |
| 71 | for (i, v) in items.iter().enumerate() { |
| 72 | if i > 0 { |
| 73 | out.push(','); |
| 74 | } |
| 75 | v.write(out); |
| 76 | } |
| 77 | out.push(']'); |
| 78 | } |
| 79 | Value::Object(fields) => { |
| 80 | out.push('{'); |
| 81 | for (i, (k, v)) in fields.iter().enumerate() { |
| 82 | if i > 0 { |
| 83 | out.push(','); |
| 84 | } |
| 85 | write_str(k, out); |
| 86 | out.push(':'); |
| 87 | v.write(out); |
| 88 | } |
| 89 | out.push('}'); |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | fn write_str(s: &str, out: &mut String) { |
| 96 | out.push('"'); |
| 97 | for c in s.chars() { |
| 98 | match c { |
| 99 | '"' => out.push_str("\\\""), |
| 100 | '\\' => out.push_str("\\\\"), |
| 101 | '\n' => out.push_str("\\n"), |
| 102 | '\r' => out.push_str("\\r"), |
| 103 | '\t' => out.push_str("\\t"), |
| 104 | c if (c as u32) < 0x20 => { |
| 105 | let _ = write!(out, "\\u{:04x}", c as u32); |
| 106 | } |
| 107 | c => out.push(c), |
| 108 | } |
| 109 | } |
| 110 | out.push('"'); |
| 111 | } |
| 112 | |
| 113 | /// Build an object: `obj([("a", 1.into()), ("b", "x".into())])`. |
| 114 | pub fn obj<const N: usize>(fields: [(&str, Value); N]) -> Value { |
| 115 | Value::Object(fields.into_iter().map(|(k, v)| (k.to_string(), v)).collect()) |
| 116 | } |
| 117 | |
| 118 | impl From<&str> for Value { |
| 119 | fn from(s: &str) -> Self { |
| 120 | Value::Str(s.to_string()) |
| 121 | } |
| 122 | } |
| 123 | impl From<String> for Value { |
| 124 | fn from(s: String) -> Self { |
| 125 | Value::Str(s) |
| 126 | } |
| 127 | } |
| 128 | impl From<i64> for Value { |
| 129 | fn from(n: i64) -> Self { |
| 130 | Value::Int(n) |
| 131 | } |
| 132 | } |
| 133 | impl From<u64> for Value { |
| 134 | fn from(n: u64) -> Self { |
| 135 | Value::Int(n as i64) |
| 136 | } |
| 137 | } |
| 138 | impl From<bool> for Value { |
| 139 | fn from(b: bool) -> Self { |
| 140 | Value::Bool(b) |
| 141 | } |
| 142 | } |
| 143 | impl<T: Into<Value>> From<Option<T>> for Value { |
| 144 | fn from(o: Option<T>) -> Self { |
| 145 | o.map_or(Value::Null, Into::into) |
| 146 | } |
| 147 | } |
| 148 | impl From<Vec<Value>> for Value { |
| 149 | fn from(v: Vec<Value>) -> Self { |
| 150 | Value::Array(v) |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | #[derive(Debug, PartialEq)] |
| 155 | pub struct ParseError { |
| 156 | pub at: usize, |
| 157 | pub msg: &'static str, |
| 158 | } |
| 159 | |
| 160 | impl std::fmt::Display for ParseError { |
| 161 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
| 162 | write!(f, "invalid JSON at byte {}: {}", self.at, self.msg) |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | /// Parse a complete JSON document. Trailing content other than whitespace is an error. |
| 167 | pub fn parse(src: &str) -> Result<Value, ParseError> { |
| 168 | let mut p = Parser { s: src.as_bytes(), i: 0, depth: 0 }; |
| 169 | p.ws(); |
| 170 | let v = p.value()?; |
| 171 | p.ws(); |
| 172 | if p.i != p.s.len() { |
| 173 | return Err(p.err("trailing characters")); |
| 174 | } |
| 175 | Ok(v) |
| 176 | } |
| 177 | |
| 178 | const MAX_DEPTH: usize = 128; |
| 179 | |
| 180 | struct Parser<'a> { |
| 181 | s: &'a [u8], |
| 182 | i: usize, |
| 183 | depth: usize, |
| 184 | } |
| 185 | |
| 186 | impl Parser<'_> { |
| 187 | fn err(&self, msg: &'static str) -> ParseError { |
| 188 | ParseError { at: self.i, msg } |
| 189 | } |
| 190 | fn ws(&mut self) { |
| 191 | while let Some(b' ' | b'\n' | b'\r' | b'\t') = self.s.get(self.i) { |
| 192 | self.i += 1; |
| 193 | } |
| 194 | } |
| 195 | fn eat(&mut self, lit: &[u8]) -> bool { |
| 196 | if self.s[self.i..].starts_with(lit) { |
| 197 | self.i += lit.len(); |
| 198 | true |
| 199 | } else { |
| 200 | false |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | fn value(&mut self) -> Result<Value, ParseError> { |
| 205 | match self.s.get(self.i) { |
| 206 | None => Err(self.err("unexpected end")), |
| 207 | Some(b'n') if self.eat(b"null") => Ok(Value::Null), |
| 208 | Some(b't') if self.eat(b"true") => Ok(Value::Bool(true)), |
| 209 | Some(b'f') if self.eat(b"false") => Ok(Value::Bool(false)), |
| 210 | Some(b'"') => Ok(Value::Str(self.string()?)), |
| 211 | Some(b'[') => self.array(), |
| 212 | Some(b'{') => self.object(), |
| 213 | Some(b'-' | b'0'..=b'9') => self.number(), |
| 214 | _ => Err(self.err("unexpected character")), |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | fn nest(&mut self) -> Result<(), ParseError> { |
| 219 | self.depth += 1; |
| 220 | if self.depth > MAX_DEPTH { |
| 221 | return Err(self.err("nested too deeply")); |
| 222 | } |
| 223 | Ok(()) |
| 224 | } |
| 225 | |
| 226 | fn array(&mut self) -> Result<Value, ParseError> { |
| 227 | self.nest()?; |
| 228 | self.i += 1; |
| 229 | let mut items = Vec::new(); |
| 230 | self.ws(); |
| 231 | if self.eat(b"]") { |
| 232 | self.depth -= 1; |
| 233 | return Ok(Value::Array(items)); |
| 234 | } |
| 235 | loop { |
| 236 | self.ws(); |
| 237 | items.push(self.value()?); |
| 238 | self.ws(); |
| 239 | if self.eat(b",") { |
| 240 | continue; |
| 241 | } |
| 242 | if self.eat(b"]") { |
| 243 | self.depth -= 1; |
| 244 | return Ok(Value::Array(items)); |
| 245 | } |
| 246 | return Err(self.err("expected , or ]")); |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | fn object(&mut self) -> Result<Value, ParseError> { |
| 251 | self.nest()?; |
| 252 | self.i += 1; |
| 253 | let mut fields: Vec<(String, Value)> = Vec::new(); |
| 254 | self.ws(); |
| 255 | if self.eat(b"}") { |
| 256 | self.depth -= 1; |
| 257 | return Ok(Value::Object(fields)); |
| 258 | } |
| 259 | loop { |
| 260 | self.ws(); |
| 261 | if self.s.get(self.i) != Some(&b'"') { |
| 262 | return Err(self.err("expected a key")); |
| 263 | } |
| 264 | let key = self.string()?; |
| 265 | if fields.iter().any(|(k, _)| *k == key) { |
| 266 | return Err(self.err("duplicate key")); |
| 267 | } |
| 268 | self.ws(); |
| 269 | if !self.eat(b":") { |
| 270 | return Err(self.err("expected :")); |
| 271 | } |
| 272 | self.ws(); |
| 273 | let v = self.value()?; |
| 274 | fields.push((key, v)); |
| 275 | self.ws(); |
| 276 | if self.eat(b",") { |
| 277 | continue; |
| 278 | } |
| 279 | if self.eat(b"}") { |
| 280 | self.depth -= 1; |
| 281 | return Ok(Value::Object(fields)); |
| 282 | } |
| 283 | return Err(self.err("expected , or }")); |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | fn number(&mut self) -> Result<Value, ParseError> { |
| 288 | let start = self.i; |
| 289 | let digits = |p: &mut Self| { |
| 290 | let s = p.i; |
| 291 | while let Some(b'0'..=b'9') = p.s.get(p.i) { |
| 292 | p.i += 1; |
| 293 | } |
| 294 | p.i - s |
| 295 | }; |
| 296 | self.eat(b"-"); |
| 297 | match self.s.get(self.i) { |
| 298 | Some(b'0') => self.i += 1, |
| 299 | Some(b'1'..=b'9') => { |
| 300 | digits(self); |
| 301 | } |
| 302 | _ => return Err(self.err("expected a digit")), |
| 303 | } |
| 304 | let mut float = false; |
| 305 | if self.eat(b".") { |
| 306 | float = true; |
| 307 | if digits(self) == 0 { |
| 308 | return Err(self.err("expected a digit after .")); |
| 309 | } |
| 310 | } |
| 311 | if let Some(b'e' | b'E') = self.s.get(self.i) { |
| 312 | float = true; |
| 313 | self.i += 1; |
| 314 | if let Some(b'+' | b'-') = self.s.get(self.i) { |
| 315 | self.i += 1; |
| 316 | } |
| 317 | if digits(self) == 0 { |
| 318 | return Err(self.err("expected a digit in exponent")); |
| 319 | } |
| 320 | } |
| 321 | let text = std::str::from_utf8(&self.s[start..self.i]).expect("ascii"); |
| 322 | if !float { |
| 323 | if let Ok(n) = text.parse::<i64>() { |
| 324 | return Ok(Value::Int(n)); |
| 325 | } |
| 326 | } |
| 327 | text.parse::<f64>().map(Value::Float).map_err(|_| self.err("bad number")) |
| 328 | } |
| 329 | |
| 330 | fn string(&mut self) -> Result<String, ParseError> { |
| 331 | self.i += 1; // opening quote |
| 332 | let mut out = String::new(); |
| 333 | loop { |
| 334 | let start = self.i; |
| 335 | while let Some(&b) = self.s.get(self.i) { |
| 336 | if b == b'"' || b == b'\\' || b < 0x20 { |
| 337 | break; |
| 338 | } |
| 339 | self.i += 1; |
| 340 | } |
| 341 | // The input is a &str, so any run between ASCII delimiters is valid UTF-8. |
| 342 | out.push_str(std::str::from_utf8(&self.s[start..self.i]).map_err(|_| self.err("bad UTF-8"))?); |
| 343 | match self.s.get(self.i) { |
| 344 | None => return Err(self.err("unterminated string")), |
| 345 | Some(b'"') => { |
| 346 | self.i += 1; |
| 347 | return Ok(out); |
| 348 | } |
| 349 | Some(b'\\') => { |
| 350 | self.i += 1; |
| 351 | let c = match self.s.get(self.i) { |
| 352 | Some(b'"') => '"', |
| 353 | Some(b'\\') => '\\', |
| 354 | Some(b'/') => '/', |
| 355 | Some(b'b') => '\u{8}', |
| 356 | Some(b'f') => '\u{c}', |
| 357 | Some(b'n') => '\n', |
| 358 | Some(b'r') => '\r', |
| 359 | Some(b't') => '\t', |
| 360 | Some(b'u') => { |
| 361 | self.i += 1; |
| 362 | let hi = self.hex4()?; |
| 363 | let code = if (0xD800..0xDC00).contains(&hi) { |
| 364 | if !self.eat(b"\\u") { |
| 365 | return Err(self.err("lone surrogate")); |
| 366 | } |
| 367 | let lo = self.hex4()?; |
| 368 | if !(0xDC00..0xE000).contains(&lo) { |
| 369 | return Err(self.err("bad surrogate pair")); |
| 370 | } |
| 371 | 0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00) |
| 372 | } else if (0xDC00..0xE000).contains(&hi) { |
| 373 | return Err(self.err("lone surrogate")); |
| 374 | } else { |
| 375 | hi |
| 376 | }; |
| 377 | out.push(char::from_u32(code).ok_or_else(|| self.err("bad escape"))?); |
| 378 | continue; |
| 379 | } |
| 380 | _ => return Err(self.err("bad escape")), |
| 381 | }; |
| 382 | self.i += 1; |
| 383 | out.push(c); |
| 384 | } |
| 385 | Some(_) => return Err(self.err("control character in string")), |
| 386 | } |
| 387 | } |
| 388 | } |
| 389 | |
| 390 | fn hex4(&mut self) -> Result<u32, ParseError> { |
| 391 | let h = self.s.get(self.i..self.i + 4).ok_or_else(|| self.err("short \\u escape"))?; |
| 392 | let s = std::str::from_utf8(h).map_err(|_| self.err("bad \\u escape"))?; |
| 393 | let n = u32::from_str_radix(s, 16).map_err(|_| self.err("bad \\u escape"))?; |
| 394 | self.i += 4; |
| 395 | Ok(n) |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | #[cfg(test)] |
| 400 | mod tests { |
| 401 | use super::*; |
| 402 | |
| 403 | #[test] |
| 404 | fn round_trips() { |
| 405 | let docs = [ |
| 406 | r#"null"#, |
| 407 | r#"[true,false,0,-12,9007199254740993,1.5,"a"]"#, |
| 408 | r#"{"b":1,"a":{"nested":[]},"s":"quote \" backslash \\ newline \n tab \t"}"#, |
| 409 | r#""unicode: é 中文 🌱""#, |
| 410 | ]; |
| 411 | for d in docs { |
| 412 | let v = parse(d).unwrap(); |
| 413 | assert_eq!(parse(&v.encode()).unwrap(), v, "{d}"); |
| 414 | } |
| 415 | // Key order and exact integers survive. |
| 416 | assert_eq!(parse(r#"{"z":1,"a":9007199254740993}"#).unwrap().encode(), r#"{"z":1,"a":9007199254740993}"#); |
| 417 | } |
| 418 | |
| 419 | #[test] |
| 420 | fn escapes() { |
| 421 | assert_eq!(parse(r#""\u00e9\ud83c\udf31\/""#).unwrap(), Value::Str("é🌱/".into())); |
| 422 | assert_eq!(Value::Str("\u{1}".into()).encode(), r#""\u0001""#); |
| 423 | } |
| 424 | |
| 425 | #[test] |
| 426 | fn rejects_invalid() { |
| 427 | for bad in [ |
| 428 | "", "[", "[1,]", "{\"a\":1,}", "01", "1.", "-", "\"\\x\"", "\"\\ud800\"", "\"a\nb\"", |
| 429 | "{\"a\":1,\"a\":2}", "nul", "[1] 2", "{a:1}", |
| 430 | ] { |
| 431 | assert!(parse(bad).is_err(), "should reject {bad:?}"); |
| 432 | } |
| 433 | let deep = "[".repeat(200) + &"]".repeat(200); |
| 434 | assert!(parse(&deep).is_err()); |
| 435 | } |
| 436 | } |