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}