facet_json/deserialize.rs
1use core::num::{
2 NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroIsize, NonZeroU8, NonZeroU16, NonZeroU32,
3 NonZeroU64, NonZeroUsize,
4};
5
6use facet_ansi::Stylize as _;
7use facet_core::{Def, Facet, ScalarAffinity};
8use facet_reflect::{HeapValue, Wip};
9use log::trace;
10
11use crate::alloc::string::ToString;
12use alloc::string::String;
13use alloc::vec::Vec;
14
15/// A JSON parse error, with context. Never would've guessed huh.
16#[derive(Debug)]
17pub struct JsonParseErrorWithContext<'input> {
18 input: &'input [u8],
19 pos: usize,
20 kind: JsonErrorKind,
21}
22
23impl<'input> JsonParseErrorWithContext<'input> {
24 /// Creates a new `JsonParseErrorWithContext`.
25 ///
26 /// # Arguments
27 ///
28 /// * `kind` - The kind of JSON error encountered.
29 /// * `input` - The original input being parsed.
30 /// * `pos` - The position in the input where the error occurred.
31 pub fn new(kind: JsonErrorKind, input: &'input [u8], pos: usize) -> Self {
32 Self { input, pos, kind }
33 }
34}
35
36/// An error kind for JSON parsing.
37#[derive(Debug)]
38pub enum JsonErrorKind {
39 /// The input ended unexpectedly while parsing JSON.
40 UnexpectedEof,
41 /// An unexpected character was encountered in the input.
42 UnexpectedCharacter(char),
43 /// A number is out of range.
44 NumberOutOfRange(f64),
45 /// An unexpected String was encountered in the input.
46 StringAsNumber(String),
47}
48
49impl core::fmt::Display for JsonParseErrorWithContext<'_> {
50 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51 let Ok(input) = core::str::from_utf8(self.input) else {
52 return write!(f, "(JSON input was invalid UTF-8)");
53 };
54
55 writeln!(f)?;
56
57 let mut pos = self.pos as isize;
58 let mut line_start = 0;
59 for (line_num, line) in input.lines().enumerate() {
60 let line_len = line.len();
61 let pos_line = pos >= 0 && pos <= line_len as isize;
62 let dist = (pos - line_start).abs();
63
64 if dist < 120 || pos_line {
65 writeln!(f, "{:>6} {}", (line_num + 1).dim(), line)?;
66 if pos_line {
67 writeln!(
68 f,
69 "{:width$} {} {:?}",
70 "",
71 "⎩".red(),
72 (&self.kind).bright_blue(),
73 width = (pos as usize + 6), // 6 for line numbers, 1 for spaces
74 )?;
75 }
76 } else if dist > 180 && pos < 0 {
77 break;
78 }
79
80 pos -= (line_len + 1) as isize; // newline counts as one byte!
81 line_start += line_len as isize;
82 }
83 Ok(())
84 }
85}
86
87impl core::error::Error for JsonParseErrorWithContext<'_> {}
88
89/// Deserializes a JSON string into a value of type `T` that implements `Facet`.
90///
91/// This function takes a JSON string representation and converts it into a Rust
92/// value of the specified type `T`. The type must implement the `Facet` trait
93/// to provide the necessary type information for deserialization.
94pub fn from_str<T: Facet>(json: &str) -> Result<T, JsonParseErrorWithContext<'_>> {
95 from_slice(json.as_bytes())
96}
97
98/// Deserialize JSON from a slice
99///
100/// # Arguments
101///
102/// * `json` - A slice of bytes representing the JSON input.
103///
104/// # Returns
105///
106/// A result containing the deserialized value of type `T` or a `JsonParseErrorWithContext`.
107pub fn from_slice<T: Facet>(json: &[u8]) -> Result<T, JsonParseErrorWithContext<'_>> {
108 let wip = Wip::alloc::<T>();
109 let heap_value = from_slice_wip(wip, json)?;
110 Ok(heap_value.materialize::<T>().unwrap())
111}
112
113/// Deserialize a JSON string into a Wip object.
114///
115/// # Arguments
116///
117/// * `wip` - A mutable Wip object to deserialize into.
118/// * `input` - A byte slice representing the JSON input.
119///
120/// # Returns
121///
122/// A result containing the updated `Wip` or a `JsonParseErrorWithContext`.
123pub fn from_slice_wip<'input, 'a>(
124 mut wip: Wip<'a>,
125 input: &'input [u8],
126) -> Result<HeapValue<'a>, JsonParseErrorWithContext<'input>> {
127 let mut pos = 0;
128
129 macro_rules! err {
130 ($kind:expr) => {
131 Err(JsonParseErrorWithContext::new($kind, input, pos))
132 };
133 }
134 macro_rules! bail {
135 ($kind:expr) => {
136 return err!($kind);
137 };
138 }
139
140 /// Indicates why we are expecting a value in the parsing stack.
141 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
142 enum WhyValue {
143 /// At the top level of the JSON input.
144 TopLevel,
145 /// Expecting an object key.
146 ObjectKey,
147 /// Expecting an object value.
148 ObjectValue,
149 /// Expecting an array element.
150 ArrayElement,
151 }
152
153 /// Indicates the context for a comma separator in JSON (object or array).
154 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
155 enum WhyComma {
156 /// A comma in an object context.
157 Object,
158 /// A comma in an array context.
159 Array,
160 }
161
162 /// Indicates the type of separator expected (colon or comma).
163 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
164 enum Separator {
165 /// Expecting a colon separator in object key-value pairs.
166 Colon,
167 /// Expecting a comma separator (in objects or arrays).
168 Comma(WhyComma),
169 }
170
171 /// Represents the next expected token or structure while parsing.
172 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
173 enum Expect {
174 /// Expecting a value, with its reason/context.
175 Value(WhyValue),
176 /// Expecting a separator (colon or comma).
177 Separator(Separator),
178 /// We did `push_some` and now we need to pop it
179 PopOption,
180 }
181
182 let mut stack: Vec<Expect> = Vec::new();
183 stack.push(Expect::Value(WhyValue::TopLevel));
184
185 loop {
186 // skip over whitespace
187 while let Some(c) = input.get(pos).copied() {
188 match c {
189 b' ' | b'\t' | b'\n' | b'\r' => {
190 pos += 1;
191 }
192 _ => break,
193 }
194 }
195
196 let frame_count = wip.frames_count();
197 let expect = match stack.pop() {
198 Some(expect) => expect,
199 None => {
200 if frame_count == 1 {
201 return Ok(wip.build().unwrap());
202 } else {
203 bail!(JsonErrorKind::UnexpectedEof);
204 }
205 }
206 };
207 trace!("[{frame_count}] Expecting {expect:?}");
208
209 let Some(c) = input.get(pos).copied() else {
210 bail!(JsonErrorKind::UnexpectedEof);
211 };
212
213 match expect {
214 Expect::PopOption => {
215 // that's all, carry on
216 trace!("Popping option");
217 wip = wip.pop().unwrap();
218 }
219 Expect::Value(why) => {
220 if let Def::Option(_) = wip.shape().def {
221 wip = wip.push_some().unwrap();
222 stack.push(Expect::PopOption);
223 }
224
225 match c {
226 b'{' => {
227 pos += 1;
228 let Some(c) = input.get(pos).copied() else {
229 bail!(JsonErrorKind::UnexpectedEof);
230 };
231 match c {
232 b'}' => {
233 pos += 1;
234 if frame_count > 1 {
235 // just finished reading a value I guess
236 wip = wip.pop().unwrap();
237 }
238 }
239 _ => {
240 // okay, next we expect a "key: value"
241 stack.push(Expect::Separator(Separator::Comma(WhyComma::Object)));
242 stack.push(Expect::Value(WhyValue::ObjectValue));
243 stack.push(Expect::Separator(Separator::Colon));
244 stack.push(Expect::Value(WhyValue::ObjectKey));
245 }
246 }
247 }
248 b'[' => {
249 pos += 1;
250 let Some(c) = input.get(pos).copied() else {
251 bail!(JsonErrorKind::UnexpectedEof);
252 };
253
254 wip = wip.begin_pushback().unwrap();
255 match c {
256 b']' => {
257 // an array just closed, somewhere
258 pos += 1;
259 }
260 _ => {
261 // okay, next we expect an item and a separator (or the end of the array)
262 stack.push(Expect::Separator(Separator::Comma(WhyComma::Array)));
263 stack.push(Expect::Value(WhyValue::ArrayElement));
264 wip = wip.push().unwrap();
265 }
266 }
267 }
268 b'"' => {
269 pos += 1;
270 // our value is a string
271 let mut value = String::new();
272 loop {
273 let Some(c) = input.get(pos).copied() else {
274 bail!(JsonErrorKind::UnexpectedEof);
275 };
276 match c {
277 b'"' => {
278 pos += 1;
279 break;
280 }
281 b'\\' => {
282 pos += 2;
283 value.push('\\');
284 }
285 _ => {
286 pos += 1;
287 value.push(c as char);
288 }
289 }
290 }
291 trace!(
292 "Parsed string value: {:?} for shape {}",
293 (&value).yellow(),
294 wip.shape()
295 );
296
297 match why {
298 WhyValue::TopLevel => {
299 wip = wip.parse(&value).unwrap();
300 }
301 WhyValue::ArrayElement => {
302 wip = wip.parse(&value).unwrap();
303 wip = wip.pop().unwrap();
304 }
305 WhyValue::ObjectValue => {
306 wip = wip.parse(&value).unwrap();
307 wip = wip.pop().unwrap();
308 }
309 WhyValue::ObjectKey => {
310 wip = wip.field_named(&value).unwrap();
311 }
312 }
313 }
314 b'0'..=b'9' | b'-' => {
315 pos += 1;
316 let start = pos - 1;
317 while let Some(c) = input.get(pos) {
318 match c {
319 b'0'..=b'9' | b'.' => {
320 pos += 1;
321 }
322 _ => break,
323 }
324 }
325 let number = &input[start..pos];
326 let number = core::str::from_utf8(number).unwrap();
327 trace!("Parsed number value: {:?}", number.yellow());
328 let number = number.parse::<f64>().unwrap();
329 trace!("Parsed number value: {:?}", number.yellow());
330
331 let shape = wip.shape();
332 match shape.def {
333 Def::Scalar(sd) => match sd.affinity {
334 ScalarAffinity::Number(_na) => {
335 if shape.is_type::<u8>() {
336 if number >= 0.0 && number <= u8::MAX as f64 {
337 let value = number as u8;
338 wip = wip.put::<u8>(value).unwrap();
339 } else {
340 bail!(JsonErrorKind::NumberOutOfRange(number));
341 }
342 } else if shape.is_type::<u16>() {
343 if number >= 0.0 && number <= u16::MAX as f64 {
344 let value = number as u16;
345 wip = wip.put::<u16>(value).unwrap();
346 } else {
347 bail!(JsonErrorKind::NumberOutOfRange(number));
348 }
349 } else if shape.is_type::<u32>() {
350 if number >= 0.0 && number <= u32::MAX as f64 {
351 let value = number as u32;
352 wip = wip.put::<u32>(value).unwrap();
353 } else {
354 bail!(JsonErrorKind::NumberOutOfRange(number));
355 }
356 } else if shape.is_type::<u64>() {
357 if number >= 0.0 && number <= u64::MAX as f64 {
358 let value = number as u64;
359 wip = wip.put::<u64>(value).unwrap();
360 } else {
361 bail!(JsonErrorKind::NumberOutOfRange(number));
362 }
363 } else if shape.is_type::<i8>() {
364 if number >= i8::MIN as f64 && number <= i8::MAX as f64 {
365 let value = number as i8;
366 wip = wip.put::<i8>(value).unwrap();
367 } else {
368 bail!(JsonErrorKind::NumberOutOfRange(number));
369 }
370 } else if shape.is_type::<i16>() {
371 if number >= i16::MIN as f64 && number <= i16::MAX as f64 {
372 let value = number as i16;
373 wip = wip.put::<i16>(value).unwrap();
374 } else {
375 bail!(JsonErrorKind::NumberOutOfRange(number));
376 }
377 } else if shape.is_type::<i32>() {
378 if number >= i32::MIN as f64 && number <= i32::MAX as f64 {
379 let value = number as i32;
380 wip = wip.put::<i32>(value).unwrap();
381 } else {
382 bail!(JsonErrorKind::NumberOutOfRange(number));
383 }
384 } else if shape.is_type::<i64>() {
385 // Note: f64 might lose precision for large i64 values, but this is a common limitation.
386 if number >= i64::MIN as f64 && number <= i64::MAX as f64 {
387 let value = number as i64;
388 wip = wip.put::<i64>(value).unwrap();
389 } else {
390 bail!(JsonErrorKind::NumberOutOfRange(number));
391 }
392 } else if shape.is_type::<f32>() {
393 if number >= f32::MIN as f64 && number <= f32::MAX as f64 {
394 let value = number as f32;
395 wip = wip.put::<f32>(value).unwrap();
396 } else {
397 bail!(JsonErrorKind::NumberOutOfRange(number));
398 }
399 } else if shape.is_type::<f64>() {
400 wip = wip.put::<f64>(number).unwrap();
401 } else if shape.is_type::<NonZeroU8>() {
402 if number >= 1.0 && number <= u8::MAX as f64 {
403 let value = NonZeroU8::new(number as u8).unwrap();
404 wip = wip.put::<NonZeroU8>(value).unwrap();
405 } else {
406 bail!(JsonErrorKind::NumberOutOfRange(number));
407 }
408 } else if shape.is_type::<NonZeroU16>() {
409 if number >= 1.0 && number <= u16::MAX as f64 {
410 let value = NonZeroU16::new(number as u16).unwrap();
411 wip = wip.put::<NonZeroU16>(value).unwrap();
412 } else {
413 bail!(JsonErrorKind::NumberOutOfRange(number));
414 }
415 } else if shape.is_type::<NonZeroU32>() {
416 if number >= 1.0 && number <= u32::MAX as f64 {
417 let value = NonZeroU32::new(number as u32).unwrap();
418 wip = wip.put::<NonZeroU32>(value).unwrap();
419 } else {
420 bail!(JsonErrorKind::NumberOutOfRange(number));
421 }
422 } else if shape.is_type::<NonZeroU64>() {
423 if number >= 1.0 && number <= u64::MAX as f64 {
424 let value = NonZeroU64::new(number as u64).unwrap();
425 wip = wip.put::<NonZeroU64>(value).unwrap();
426 } else {
427 bail!(JsonErrorKind::NumberOutOfRange(number));
428 }
429 } else if shape.is_type::<NonZeroUsize>() {
430 if number >= 1.0 && number <= usize::MAX as f64 {
431 let value = NonZeroUsize::new(number as usize).unwrap();
432 wip = wip.put::<NonZeroUsize>(value).unwrap();
433 } else {
434 bail!(JsonErrorKind::NumberOutOfRange(number));
435 }
436 } else if shape.is_type::<NonZeroI8>() {
437 if number >= 1.0 && number <= i8::MAX as f64 {
438 let value = NonZeroI8::new(number as i8).unwrap();
439 wip = wip.put::<NonZeroI8>(value).unwrap();
440 } else {
441 bail!(JsonErrorKind::NumberOutOfRange(number));
442 }
443 } else if shape.is_type::<NonZeroI16>() {
444 if number >= 1.0 && number <= i16::MAX as f64 {
445 let value = NonZeroI16::new(number as i16).unwrap();
446 wip = wip.put::<NonZeroI16>(value).unwrap();
447 } else {
448 bail!(JsonErrorKind::NumberOutOfRange(number));
449 }
450 } else if shape.is_type::<NonZeroI32>() {
451 if number >= 1.0 && number <= i32::MAX as f64 {
452 let value = NonZeroI32::new(number as i32).unwrap();
453 wip = wip.put::<NonZeroI32>(value).unwrap();
454 } else {
455 bail!(JsonErrorKind::NumberOutOfRange(number));
456 }
457 } else if shape.is_type::<NonZeroI64>() {
458 if number >= 1.0 && number <= i64::MAX as f64 {
459 let value = NonZeroI64::new(number as i64).unwrap();
460 wip = wip.put::<NonZeroI64>(value).unwrap();
461 } else {
462 bail!(JsonErrorKind::NumberOutOfRange(number));
463 }
464 } else if shape.is_type::<NonZeroIsize>() {
465 if number >= 1.0 && number <= isize::MAX as f64 {
466 let value = NonZeroIsize::new(number as isize).unwrap();
467 wip = wip.put::<NonZeroIsize>(value).unwrap();
468 } else {
469 bail!(JsonErrorKind::NumberOutOfRange(number));
470 }
471 } else {
472 todo!("number type, but unknown")
473 }
474 }
475 ScalarAffinity::String(_sa) => {
476 if shape.is_type::<String>() {
477 let value = number.to_string();
478 bail!(JsonErrorKind::StringAsNumber(value));
479 } else {
480 todo!()
481 }
482 }
483 _ => {
484 todo!("saw number in JSON but expected.. shape {}?", shape)
485 }
486 },
487 _ => {
488 todo!("saw number in JSON but expected.. shape {}?", shape)
489 }
490 }
491
492 match why {
493 WhyValue::TopLevel => {}
494 WhyValue::ObjectKey => todo!(),
495 WhyValue::ObjectValue => {
496 wip = wip.pop().unwrap();
497 }
498 WhyValue::ArrayElement => {
499 wip = wip.pop().unwrap();
500 }
501 }
502 }
503 b'n' => {
504 // wow it's a null — probably
505 let slice_rest = &input[pos..];
506 if slice_rest.starts_with(b"null") {
507 pos += 4;
508
509 // ok but we already pushed some! luckily wip has the method for us
510 wip = wip.pop_some_push_none().unwrap();
511
512 match why {
513 WhyValue::TopLevel => {}
514 WhyValue::ObjectKey => todo!(),
515 WhyValue::ObjectValue => {
516 // these are all super messy, they should be expect on the stack
517 wip = wip.pop().unwrap();
518 }
519 WhyValue::ArrayElement => {
520 wip = wip.pop().unwrap();
521 }
522 }
523 } else {
524 bail!(JsonErrorKind::UnexpectedCharacter('n'));
525 }
526 }
527 c => {
528 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
529 }
530 }
531 }
532 Expect::Separator(separator) => match separator {
533 Separator::Colon => match c {
534 b':' => {
535 pos += 1;
536 }
537 _ => {
538 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
539 }
540 },
541 Separator::Comma(why) => match c {
542 b',' => {
543 pos += 1;
544 match why {
545 WhyComma::Array => {
546 stack.push(Expect::Separator(Separator::Comma(WhyComma::Array)));
547 stack.push(Expect::Value(WhyValue::ArrayElement));
548 wip = wip.push().unwrap();
549 }
550 WhyComma::Object => {
551 // looks like we're in for another round of object parsing
552 stack.push(Expect::Separator(Separator::Comma(WhyComma::Object)));
553 stack.push(Expect::Value(WhyValue::ObjectValue));
554 stack.push(Expect::Separator(Separator::Colon));
555 stack.push(Expect::Value(WhyValue::ObjectKey));
556 }
557 }
558 }
559 b'}' => {
560 match why {
561 WhyComma::Object => {
562 pos += 1;
563
564 // we finished the object, neat
565 if frame_count > 1 {
566 wip = wip.pop().unwrap();
567 }
568 }
569 _ => {
570 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
571 }
572 }
573 }
574 b']' => {
575 pos += 1;
576 match why {
577 WhyComma::Array => {
578 // we finished the array, neat
579 if frame_count > 1 {
580 wip = wip.pop().unwrap();
581 }
582 }
583 _ => {
584 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
585 }
586 }
587 }
588 _ => {
589 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
590 }
591 },
592 },
593 }
594 }
595}