facet_json/
deserialize.rs

1use alloc::string::String;
2use alloc::string::ToString;
3use alloc::vec;
4use facet_core::Characteristic;
5use facet_core::Def;
6use facet_core::Facet;
7use facet_reflect::ReflectError;
8use facet_reflect::{HeapValue, Wip};
9use log::trace;
10use owo_colors::OwoColorize;
11
12mod tokenizer;
13pub use tokenizer::*;
14
15mod error;
16pub use error::*;
17
18/// Deserializes a JSON string into a value of type `T` that implements `Facet`.
19///
20/// This function takes a JSON string representation and converts it into a Rust
21/// value of the specified type `T`. The type must implement the `Facet` trait
22/// to provide the necessary type information for deserialization.
23pub fn from_str<T: Facet>(json: &str) -> Result<T, JsonError<'_>> {
24    from_slice(json.as_bytes())
25}
26
27/// Deserialize JSON from a slice
28///
29/// # Arguments
30///
31/// * `json` - A slice of bytes representing the JSON input.
32///
33/// # Returns
34///
35/// A result containing the deserialized value of type `T` or a `JsonParseErrorWithContext`.
36pub fn from_slice<T: Facet>(json: &[u8]) -> Result<T, JsonError<'_>> {
37    let wip = Wip::alloc::<T>();
38    let heap_value = from_slice_wip(wip, json)?;
39    Ok(heap_value.materialize::<T>().unwrap())
40}
41
42/// Represents the next expected token or structure while parsing.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44enum Instruction {
45    Value,
46    SkipValue,
47    Pop(PopReason),
48    ObjectKeyOrObjectClose,
49    CommaThenObjectKeyOrObjectClose,
50    ArrayItemOrArrayClose,
51    CommaThenArrayItemOrArrayClose,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55enum PopReason {
56    TopLevel,
57    ObjectVal,
58    ArrayItem,
59    Some,
60}
61
62/// Deserialize a JSON string into a Wip object.
63///
64/// # Arguments
65///
66/// * `wip` - A mutable Wip object to deserialize into.
67/// * `input` - A byte slice representing the JSON input.
68///
69/// # Returns
70///
71/// A result containing the updated `Wip` or a `JsonParseErrorWithContext`.
72pub fn from_slice_wip<'input, 'a>(
73    mut wip: Wip<'a>,
74    input: &'input [u8],
75) -> Result<HeapValue<'a>, JsonError<'input>> {
76    let mut stack = vec![Instruction::Pop(PopReason::TopLevel), Instruction::Value];
77    let mut tokenizer = Tokenizer::new(input);
78    let mut last_span = Span { start: 0, len: 0 };
79    let mut unread_token: Option<Spanned<Token>> = None;
80
81    macro_rules! bail {
82        ($kind:expr) => {
83            return Err(JsonError::new($kind, input, last_span, wip.path()))
84        };
85    }
86
87    macro_rules! read_token {
88        () => {
89            if let Some(token) = unread_token.take() {
90                last_span = token.span;
91                token
92            } else {
93                match tokenizer.next_token() {
94                    Ok(token) => {
95                        last_span = token.span;
96                        token
97                    }
98                    Err(e) => {
99                        last_span = e.span;
100                        bail!(JsonErrorKind::SyntaxError(e.kind));
101                    }
102                }
103            }
104        };
105    }
106
107    macro_rules! put_back_token {
108        ($token:expr) => {
109            assert!(
110                unread_token.is_none(),
111                "Cannot put back more than one token at a time"
112            );
113            unread_token = Some($token);
114        };
115    }
116
117    macro_rules! reflect {
118        ($($tt:tt)*) => {
119            let path = wip.path();
120            wip = match wip.$($tt)* {
121                Ok(wip) => wip,
122                Err(e) => {
123                    return Err(JsonError::new(
124                        JsonErrorKind::ReflectError(e),
125                        input,
126                        last_span,
127                        path,
128                    ));
129                }
130            }
131        };
132    }
133
134    loop {
135        let frame_count = wip.frames_count();
136        let insn = match stack.pop() {
137            Some(insn) => insn,
138            None => unreachable!("Instruction stack is empty"),
139        };
140        trace!("[{frame_count}] Instruction {:?}", insn.yellow());
141
142        match insn {
143            Instruction::Pop(reason) => {
144                trace!("Popping because {:?}", reason.yellow());
145
146                let container_shape = wip.shape();
147                match container_shape.def {
148                    Def::Struct(sd) => {
149                        let mut has_unset = false;
150
151                        trace!("Let's check all fields are initialized");
152                        for (index, field) in sd.fields.iter().enumerate() {
153                            let is_set = wip.is_field_set(index).map_err(|err| {
154                                trace!("Error checking field set status: {:?}", err);
155                                JsonError::new(
156                                    JsonErrorKind::ReflectError(err),
157                                    input,
158                                    last_span,
159                                    wip.path(),
160                                )
161                            })?;
162                            if !is_set {
163                                if let Some(default_in_place_fn) = field.maybe_default_fn() {
164                                    reflect!(field(index));
165                                    if let Some(default_in_place_fn) = default_in_place_fn {
166                                        reflect!(put_from_fn(default_in_place_fn));
167                                        trace!(
168                                            "Field #{} {:?} was set to default value (via custom fn)",
169                                            index.yellow(),
170                                            field.blue()
171                                        );
172                                    } else {
173                                        if !field.shape().is(Characteristic::Default) {
174                                            return Err(JsonError::new(
175                                                JsonErrorKind::ReflectError(
176                                                    ReflectError::DefaultAttrButNoDefaultImpl {
177                                                        shape: field.shape(),
178                                                    },
179                                                ),
180                                                input,
181                                                last_span,
182                                                wip.path(),
183                                            ));
184                                        }
185                                        reflect!(put_default());
186                                        trace!(
187                                            "Field #{} {:?} was set to default value (via default impl)",
188                                            index.yellow(),
189                                            field.blue()
190                                        );
191                                    }
192                                    reflect!(pop());
193                                } else {
194                                    trace!(
195                                        "Field #{} {:?} is not initialized",
196                                        index.yellow(),
197                                        field.blue()
198                                    );
199                                    has_unset = true;
200                                }
201                            }
202                        }
203
204                        if has_unset && container_shape.has_default_attr() {
205                            // let's allocate and build a default value
206                            let default_val = Wip::alloc_shape(container_shape)
207                                .put_default()
208                                .map_err(|e| {
209                                    JsonError::new(
210                                        JsonErrorKind::ReflectError(e),
211                                        input,
212                                        last_span,
213                                        wip.path(),
214                                    )
215                                })?
216                                .build()
217                                .map_err(|e| {
218                                    JsonError::new(
219                                        JsonErrorKind::ReflectError(e),
220                                        input,
221                                        last_span,
222                                        wip.path(),
223                                    )
224                                })?;
225                            let peek = default_val.peek().into_struct().unwrap();
226
227                            for (index, field) in sd.fields.iter().enumerate() {
228                                let is_set = wip.is_field_set(index).map_err(|err| {
229                                    trace!("Error checking field set status: {:?}", err);
230                                    JsonError::new(
231                                        JsonErrorKind::ReflectError(err),
232                                        input,
233                                        last_span,
234                                        wip.path(),
235                                    )
236                                })?;
237                                if !is_set {
238                                    let address_of_field_from_default =
239                                        peek.field(index).unwrap().data();
240                                    reflect!(field(index));
241                                    reflect!(put_shape(
242                                        address_of_field_from_default,
243                                        field.shape()
244                                    ));
245                                    reflect!(pop());
246                                }
247                            }
248                        }
249                    }
250                    Def::Enum(_) => {
251                        trace!(
252                            "TODO: make sure enums are initialized (support container-level and field-level default, etc.)"
253                        );
254                    }
255                    _ => {
256                        trace!("Thing being popped is not a container I guess");
257                    }
258                }
259
260                if reason == PopReason::TopLevel {
261                    let path = wip.path();
262                    return Ok(match wip.build() {
263                        Ok(hv) => hv,
264                        Err(e) => {
265                            return Err(JsonError::new(
266                                JsonErrorKind::ReflectError(e),
267                                input,
268                                last_span,
269                                path,
270                            ));
271                        }
272                    });
273                } else {
274                    reflect!(pop());
275                }
276            }
277            Instruction::SkipValue => {
278                let token = read_token!();
279                match token.node {
280                    Token::LBrace | Token::LBracket => {
281                        // Skip a compound value by tracking nesting depth
282                        let mut depth = 1;
283                        while depth > 0 {
284                            let token = read_token!();
285                            match token.node {
286                                Token::LBrace | Token::LBracket => {
287                                    depth += 1;
288                                }
289                                Token::RBrace | Token::RBracket => {
290                                    depth -= 1;
291                                }
292                                _ => {
293                                    // primitives, commas, colons, strings, numbers, etc.
294                                }
295                            }
296                        }
297                    }
298                    Token::String(_)
299                    | Token::F64(_)
300                    | Token::I64(_)
301                    | Token::U64(_)
302                    | Token::True
303                    | Token::False
304                    | Token::Null => {
305                        // Primitive value; nothing more to skip
306                    }
307                    other => {
308                        // Unexpected token when skipping a value
309                        bail!(JsonErrorKind::UnexpectedToken {
310                            got: other,
311                            wanted: "value"
312                        });
313                    }
314                }
315            }
316            Instruction::Value => {
317                let token = read_token!();
318                match token.node {
319                    Token::Null => {
320                        reflect!(put_default());
321                    }
322                    _ => {
323                        if matches!(wip.shape().def, Def::Option(_)) {
324                            trace!("Starting Some(_) option for {}", wip.shape().blue());
325                            reflect!(push_some());
326                            stack.push(Instruction::Pop(PopReason::Some))
327                        }
328
329                        match token.node {
330                            Token::Null => unreachable!(),
331                            Token::LBrace => {
332                                match wip.shape().def {
333                                    Def::Map(_md) => {
334                                        trace!(
335                                            "Object starting for map value ({})!",
336                                            wip.shape().blue()
337                                        );
338                                        reflect!(put_default());
339                                    }
340                                    Def::Enum(_ed) => {
341                                        trace!(
342                                            "Object starting for enum value ({})!",
343                                            wip.shape().blue()
344                                        );
345                                        // nothing to do here
346                                    }
347                                    Def::Struct(_) => {
348                                        trace!(
349                                            "Object starting for struct value ({})!",
350                                            wip.shape().blue()
351                                        );
352                                        // nothing to do here
353                                    }
354                                    _ => {
355                                        bail!(JsonErrorKind::UnsupportedType {
356                                            got: wip.shape(),
357                                            wanted: "map, enum, or struct"
358                                        });
359                                    }
360                                }
361
362                                stack.push(Instruction::ObjectKeyOrObjectClose)
363                            }
364                            Token::LBracket => {
365                                match wip.shape().def {
366                                    Def::Array(_) => {
367                                        trace!(
368                                            "Array starting for array ({})!",
369                                            wip.shape().blue()
370                                        );
371                                    }
372                                    Def::Slice(_) => {
373                                        trace!(
374                                            "Array starting for slice ({})!",
375                                            wip.shape().blue()
376                                        );
377                                    }
378                                    Def::List(_) => {
379                                        trace!("Array starting for list ({})!", wip.shape().blue());
380                                        reflect!(put_default());
381                                    }
382                                    _ => {
383                                        bail!(JsonErrorKind::UnsupportedType {
384                                            got: wip.shape(),
385                                            wanted: "array, list, or slice"
386                                        });
387                                    }
388                                }
389
390                                trace!("Beginning pushback");
391                                reflect!(begin_pushback());
392                                stack.push(Instruction::ArrayItemOrArrayClose)
393                            }
394                            Token::RBrace | Token::RBracket | Token::Colon | Token::Comma => {
395                                bail!(JsonErrorKind::UnexpectedToken {
396                                    got: token.node,
397                                    wanted: "value"
398                                });
399                            }
400                            Token::String(s) => match wip.shape().def {
401                                Def::Scalar(_sd) => {
402                                    reflect!(put::<String>(s));
403                                }
404                                Def::Enum(_ed) => {
405                                    if wip.selected_variant().is_some() {
406                                        // just put, then — if it's a tuple field it'll work
407                                        reflect!(put::<String>(s));
408                                    } else {
409                                        match wip.find_variant(&s) {
410                                            Some((variant_index, _)) => {
411                                                reflect!(variant(variant_index));
412                                            }
413                                            None => {
414                                                bail!(JsonErrorKind::NoSuchVariant {
415                                                    name: s.to_string(),
416                                                    enum_shape: wip.shape()
417                                                });
418                                            }
419                                        }
420                                    }
421                                }
422                                _ => bail!(JsonErrorKind::UnsupportedType {
423                                    got: wip.shape(),
424                                    wanted: "enum or string"
425                                }),
426                            },
427                            Token::F64(n) => {
428                                reflect!(put(n));
429                            }
430                            Token::U64(n) => {
431                                reflect!(put(n));
432                            }
433                            Token::I64(n) => {
434                                reflect!(put(n));
435                            }
436                            Token::True => {
437                                reflect!(put::<bool>(true));
438                            }
439                            Token::False => {
440                                reflect!(put::<bool>(false));
441                            }
442                            Token::EOF => {
443                                bail!(JsonErrorKind::UnexpectedEof("in value"));
444                            }
445                        }
446                    }
447                }
448            }
449            Instruction::ObjectKeyOrObjectClose => {
450                let token = read_token!();
451                match token.node {
452                    Token::String(key) => {
453                        trace!("Object key: {}", key);
454
455                        let mut ignore = false;
456                        let mut transparent = false;
457
458                        match wip.shape().def {
459                            Def::Struct(_) => match wip.field_index(&key) {
460                                Some(index) => {
461                                    reflect!(field(index));
462                                }
463                                None => {
464                                    if wip.shape().has_deny_unknown_fields_attr() {
465                                        // well, it all depends.
466                                        bail!(JsonErrorKind::UnknownField {
467                                            field_name: key.to_string(),
468                                            shape: wip.shape(),
469                                        })
470                                    } else {
471                                        trace!("Will ignore key ");
472                                        ignore = true;
473                                    }
474                                }
475                            },
476                            Def::Enum(_sd) => match wip.find_variant(&key) {
477                                Some((index, variant)) => {
478                                    trace!("Variant {} selected", variant.name.blue());
479                                    reflect!(variant(index));
480                                    transparent = true;
481                                }
482                                None => {
483                                    bail!(JsonErrorKind::NoSuchVariant {
484                                        name: key.to_string(),
485                                        enum_shape: wip.shape()
486                                    });
487                                }
488                            },
489                            Def::Map(_) => {
490                                reflect!(push_map_key());
491                                reflect!(put(key));
492                                reflect!(push_map_value());
493                            }
494                            _ => {
495                                bail!(JsonErrorKind::Unimplemented(
496                                    "object key for non-struct/map"
497                                ));
498                            }
499                        }
500
501                        let colon = read_token!();
502                        if colon.node != Token::Colon {
503                            bail!(JsonErrorKind::UnexpectedToken {
504                                got: colon.node,
505                                wanted: "colon"
506                            });
507                        }
508                        stack.push(Instruction::CommaThenObjectKeyOrObjectClose);
509                        if ignore {
510                            stack.push(Instruction::SkipValue);
511                        } else {
512                            if transparent {
513                                trace!(
514                                    "Transparent wrapper (like an outer-tagged enum), not pushing Pop insn to stack"
515                                )
516                            } else {
517                                stack.push(Instruction::Pop(PopReason::ObjectVal));
518                            }
519                            stack.push(Instruction::Value);
520                        }
521                    }
522                    Token::RBrace => {
523                        trace!("Object closing");
524                    }
525                    _ => {
526                        bail!(JsonErrorKind::UnexpectedToken {
527                            got: token.node,
528                            wanted: "object key or closing brace"
529                        });
530                    }
531                }
532            }
533            Instruction::CommaThenObjectKeyOrObjectClose => {
534                let token = read_token!();
535                match token.node {
536                    Token::Comma => {
537                        trace!("Object comma");
538                        stack.push(Instruction::ObjectKeyOrObjectClose);
539                    }
540                    Token::RBrace => {
541                        trace!("Object close");
542                    }
543                    _ => {
544                        bail!(JsonErrorKind::UnexpectedToken {
545                            got: token.node,
546                            wanted: "comma"
547                        });
548                    }
549                }
550            }
551            Instruction::ArrayItemOrArrayClose => {
552                let token = read_token!();
553                match token.node {
554                    Token::RBracket => {
555                        trace!("Array close");
556                    }
557                    _ => {
558                        trace!("Array item");
559                        put_back_token!(token);
560                        reflect!(begin_pushback());
561                        reflect!(push());
562
563                        stack.push(Instruction::CommaThenArrayItemOrArrayClose);
564                        stack.push(Instruction::Pop(PopReason::ArrayItem));
565                        stack.push(Instruction::Value);
566                    }
567                }
568            }
569            Instruction::CommaThenArrayItemOrArrayClose => {
570                let token = read_token!();
571                match token.node {
572                    Token::RBracket => {
573                        trace!("Array close");
574                    }
575                    Token::Comma => {
576                        trace!("Array comma");
577                        reflect!(push());
578                        stack.push(Instruction::CommaThenArrayItemOrArrayClose);
579                        stack.push(Instruction::Pop(PopReason::ArrayItem));
580                        stack.push(Instruction::Value);
581                    }
582                    _ => {
583                        bail!(JsonErrorKind::UnexpectedToken {
584                            got: token.node,
585                            wanted: "comma or closing bracket"
586                        });
587                    }
588                }
589            }
590        }
591    }
592}