facet_json/deserialize.rs
1use facet_core::{Characteristic, Def, Facet, FieldAttribute, ScalarAffinity, ShapeAttribute};
2use facet_reflect::{HeapValue, Wip};
3use log::trace;
4
5use alloc::format;
6use alloc::string::{String, ToString};
7use alloc::vec::Vec;
8use owo_colors::OwoColorize;
9
10mod error;
11pub use error::*;
12
13/// Deserializes a JSON string into a value of type `T` that implements `Facet`.
14///
15/// This function takes a JSON string representation and converts it into a Rust
16/// value of the specified type `T`. The type must implement the `Facet` trait
17/// to provide the necessary type information for deserialization.
18pub fn from_str<T: Facet>(json: &str) -> Result<T, JsonParseErrorWithContext<'_>> {
19 from_slice(json.as_bytes())
20}
21
22/// Deserialize JSON from a slice
23///
24/// # Arguments
25///
26/// * `json` - A slice of bytes representing the JSON input.
27///
28/// # Returns
29///
30/// A result containing the deserialized value of type `T` or a `JsonParseErrorWithContext`.
31pub fn from_slice<T: Facet>(json: &[u8]) -> Result<T, JsonParseErrorWithContext<'_>> {
32 let wip = Wip::alloc::<T>();
33 let heap_value = from_slice_wip(wip, json)?;
34 Ok(heap_value.materialize::<T>().unwrap())
35}
36
37/// Deserialize a JSON string into a Wip object.
38///
39/// # Arguments
40///
41/// * `wip` - A mutable Wip object to deserialize into.
42/// * `input` - A byte slice representing the JSON input.
43///
44/// # Returns
45///
46/// A result containing the updated `Wip` or a `JsonParseErrorWithContext`.
47pub fn from_slice_wip<'input, 'a>(
48 mut wip: Wip<'a>,
49 input: &'input [u8],
50) -> Result<HeapValue<'a>, JsonParseErrorWithContext<'input>> {
51 let mut pos = 0;
52
53 macro_rules! err {
54 ($kind:expr) => {
55 Err(JsonParseErrorWithContext::new(
56 $kind,
57 input,
58 pos,
59 wip.path(),
60 ))
61 };
62 }
63 macro_rules! bail {
64 ($kind:expr) => {
65 return err!($kind)
66 };
67 }
68
69 /// Indicates why we are expecting a value in the parsing stack.
70 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
71 enum WhyValue {
72 /// At the top level of the JSON input.
73 TopLevel,
74 /// Expecting an object key.
75 ObjectKey,
76 /// Expecting an object value.
77 ObjectValue,
78 /// Expecting an array element.
79 ArrayElement,
80 }
81
82 /// Indicates the context for a comma separator in JSON (object or array).
83 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
84 enum WhyComma {
85 /// A comma in an object context.
86 Object,
87 /// A comma in an array context.
88 Array,
89 }
90
91 /// Indicates the type of separator expected (colon or comma).
92 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
93 enum Separator {
94 /// Expecting a colon separator in object key-value pairs.
95 Colon,
96 /// Expecting a comma separator (in objects or arrays).
97 Comma(WhyComma),
98 }
99
100 /// Represents the next expected token or structure while parsing.
101 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
102 enum Expect {
103 /// Expecting a value, with its reason/context.
104 Value(WhyValue),
105 /// Expecting a separator (colon or comma).
106 Separator(Separator),
107 /// We did `push_some` and now we need to pop it
108 PopOption,
109 }
110
111 let mut stack: Vec<Expect> = Vec::new();
112 stack.push(Expect::Value(WhyValue::TopLevel));
113
114 loop {
115 // skip over whitespace
116 while let Some(c) = input.get(pos).copied() {
117 match c {
118 b' ' | b'\t' | b'\n' | b'\r' => {
119 pos += 1;
120 }
121 _ => break,
122 }
123 }
124
125 let frame_count = wip.frames_count();
126 let expect = match stack.pop() {
127 Some(expect) => expect,
128 None => {
129 if frame_count == 1 {
130 return Ok(wip.build().unwrap());
131 } else {
132 trace!("frame_count isn't 1, it's {}", frame_count);
133 bail!(JsonErrorKind::UnexpectedEof("frame_count isn't 1"));
134 }
135 }
136 };
137 trace!("[{frame_count}] Expecting {:?}", expect.yellow());
138
139 let Some(c) = input.get(pos).copied() else {
140 bail!(JsonErrorKind::UnexpectedEof("no input at pos"));
141 };
142
143 let mut finished_value: Option<WhyValue> = None;
144
145 match expect {
146 Expect::PopOption => {
147 // that's all, carry on
148 trace!("Popping option");
149 finished_value = Some(WhyValue::ObjectValue);
150 }
151 Expect::Value(why) => {
152 if let Def::Option(_) = wip.shape().def {
153 wip = wip.push_some().unwrap();
154 stack.push(Expect::PopOption);
155 }
156
157 match c {
158 b'{' => {
159 trace!("Object starting");
160 pos += 1;
161 let Some(c) = input.get(pos).copied() else {
162 bail!(JsonErrorKind::UnexpectedEof("nothing after {"));
163 };
164 match c {
165 b'}' => {
166 trace!("Empty object ended");
167 pos += 1;
168 finished_value = Some(why);
169 }
170 _ => {
171 trace!("Object's not empty, let's do `key: value ,` next");
172 // okay, next we expect a "key: value"
173 stack.push(Expect::Separator(Separator::Comma(WhyComma::Object)));
174 stack.push(Expect::Value(WhyValue::ObjectValue));
175 stack.push(Expect::Separator(Separator::Colon));
176 stack.push(Expect::Value(WhyValue::ObjectKey));
177 }
178 }
179 }
180 b'[' => {
181 pos += 1;
182 let Some(c) = input.get(pos).copied() else {
183 bail!(JsonErrorKind::UnexpectedEof("nothing after ["));
184 };
185
186 wip = wip.begin_pushback().unwrap();
187 match c {
188 b']' => {
189 // an array just closed, somewhere
190 pos += 1;
191 trace!("Got empty array");
192 finished_value = Some(why);
193 }
194 _ => {
195 // okay, next we expect an item and a separator (or the end of the array)
196 stack.push(Expect::Separator(Separator::Comma(WhyComma::Array)));
197 stack.push(Expect::Value(WhyValue::ArrayElement));
198 wip = wip.push().unwrap();
199 continue; // we didn't finish a value so don't pop yet
200 }
201 }
202 }
203 b'"' => {
204 pos += 1;
205 let start = pos;
206 // Our value is a string: collect bytes first
207 let mut bytes = Vec::new();
208 loop {
209 let Some(c) = input.get(pos).copied() else {
210 bail!(JsonErrorKind::UnexpectedEof("nothing after \""));
211 };
212 match c {
213 b'"' => {
214 break;
215 }
216 b'\\' => {
217 // Handle escape sequences
218 if let Some(&next) = input.get(pos + 1) {
219 if bytes.is_empty() {
220 bytes.extend(&input[start..pos]);
221 }
222 bytes.push(next);
223 pos += 2;
224 } else {
225 bail!(JsonErrorKind::UnexpectedEof("nothing after \\"));
226 }
227 }
228 _ => {
229 if !bytes.is_empty() {
230 bytes.push(c);
231 }
232 pos += 1;
233 }
234 }
235 }
236 let end = pos;
237 pos += 1;
238
239 let value = if bytes.is_empty() {
240 match core::str::from_utf8(&input[start..end]) {
241 Ok(it) => alloc::borrow::Cow::Borrowed(it),
242 Err(e) => bail!(JsonErrorKind::InvalidUtf8(format!(
243 "Invalid UTF-8 sequence: {}",
244 e
245 ))),
246 }
247 } else {
248 // Convert collected bytes to string at once
249 match alloc::string::String::from_utf8(bytes) {
250 Ok(it) => alloc::borrow::Cow::Owned(it),
251 Err(e) => bail!(JsonErrorKind::InvalidUtf8(format!(
252 "Invalid UTF-8 sequence: {}",
253 e
254 ))),
255 }
256 };
257
258 trace!(
259 "Parsed string {:?} for {} (why? {:?})",
260 value.yellow(),
261 wip.shape().blue(),
262 why.cyan()
263 );
264
265 match why {
266 WhyValue::TopLevel | WhyValue::ArrayElement | WhyValue::ObjectValue => {
267 // skip the string parse impl
268 if wip.current_is_type::<String>() {
269 wip = wip.put::<String>(value.into_owned()).unwrap();
270 } else {
271 wip = wip.parse(&value).unwrap();
272 }
273 finished_value = Some(why);
274 }
275 WhyValue::ObjectKey => {
276 // Look for field with matching name or rename attribute
277 let shape = wip.shape();
278
279 if let Def::Struct(struct_def) = shape.def {
280 let field = struct_def.fields.iter().find(|f| {
281 // Check original name
282 if f.name == value {
283 return true;
284 }
285
286 // Check rename attribute
287 f.attributes.iter().any(|attr| {
288 if let &FieldAttribute::Rename(rename) = attr {
289 rename == value
290 } else {
291 false
292 }
293 })
294 });
295
296 if let Some(field) = field {
297 trace!("found field {:?}", field.blue());
298 wip = wip.field_named(field.name).unwrap();
299 } else if shape.attributes.iter().any(|attr| {
300 matches!(attr, ShapeAttribute::DenyUnknownFields)
301 }) {
302 // Field not found - original or renamed, and unknown fields denied
303 bail!(JsonErrorKind::UnknownField(value.into_owned()));
304 } else {
305 // pop Expect::Colon (assert)
306 let expect_colon = stack.pop();
307 assert!(matches!(
308 expect_colon,
309 Some(Expect::Separator(Separator::Colon))
310 ));
311 // skip over whitespace
312 while let Some(b' ' | b'\t' | b'\n' | b'\r') =
313 input.get(pos).copied()
314 {
315 pos += 1;
316 }
317 // skip over colon
318 if let Some(b':') = input.get(pos) {
319 pos += 1;
320 } else {
321 bail!(JsonErrorKind::UnexpectedCharacter(
322 input
323 .get(pos)
324 .copied()
325 .map(|c| c as char)
326 .unwrap_or('\0')
327 ));
328 }
329 // skip over whitespace
330 while let Some(b' ' | b'\t' | b'\n' | b'\r') =
331 input.get(pos).copied()
332 {
333 pos += 1;
334 }
335 // pop Expect::Value
336 let expect_value = stack.pop();
337 assert!(matches!(
338 expect_value,
339 Some(Expect::Value(WhyValue::ObjectValue))
340 ));
341 // skip over value
342 skip_over_value(&mut pos, input).map_err(|e| {
343 JsonParseErrorWithContext::new(
344 e,
345 input,
346 pos,
347 wip.path(),
348 )
349 })?;
350 trace!(
351 "immediately after skip over value, we're at pos {}, char is {}",
352 pos,
353 input.get(pos).copied().unwrap_or(b'$') as char
354 );
355 }
356 } else {
357 trace!(
358 "Getting field {}, not in a Struct, but in a... {}",
359 value.blue(),
360 wip.shape()
361 );
362 wip = wip.field_named(&value).expect("assuming only structs have a fixed set of fields (which is not true, cf. enums)");
363 }
364 }
365 }
366 }
367 b'0'..=b'9' | b'-' => {
368 pos += 1;
369 let start = pos - 1;
370 while let Some(c) = input.get(pos) {
371 match c {
372 b'0'..=b'9' | b'.' => {
373 pos += 1;
374 }
375 _ => break,
376 }
377 }
378 let number = &input[start..pos];
379 let number = core::str::from_utf8(number).unwrap();
380 let number = number.parse::<f64>().unwrap();
381 trace!("Parsed {:?}", number.yellow());
382
383 // Prefer try_put_f64 only if actually supported (can_put_f64)
384 if wip.can_put_f64() {
385 wip = wip.try_put_f64(number).unwrap();
386 } else {
387 // If string affinity (eg, expecting a string, but got number)
388 let shape = wip.shape();
389 if let Def::Scalar(sd) = shape.def {
390 if let ScalarAffinity::String(_) = sd.affinity {
391 if shape.is_type::<String>() {
392 let value = number.to_string();
393 bail!(JsonErrorKind::StringAsNumber(value));
394 }
395 }
396 }
397 // fallback for other shape mismatch (todo! or parse error)
398 bail!(JsonErrorKind::NumberOutOfRange(number));
399 }
400 finished_value = Some(why);
401 }
402 b't' | b'f' => {
403 // Boolean: true or false
404 if input[pos..].starts_with(b"true") {
405 pos += 4;
406 let shape = wip.shape();
407 match shape.def {
408 Def::Scalar(sd) => match sd.affinity {
409 ScalarAffinity::Boolean(_) => {
410 wip = wip.put::<bool>(true).unwrap();
411 }
412 _ => {
413 bail!(JsonErrorKind::UnexpectedCharacter('t'));
414 }
415 },
416 _ => {
417 bail!(JsonErrorKind::UnexpectedCharacter('t'));
418 }
419 }
420 finished_value = Some(why);
421 } else if input[pos..].starts_with(b"false") {
422 pos += 5;
423 let shape = wip.shape();
424 match shape.def {
425 Def::Scalar(sd) => match sd.affinity {
426 ScalarAffinity::Boolean(_) => {
427 wip = wip.put::<bool>(false).unwrap();
428 }
429 _ => {
430 bail!(JsonErrorKind::UnexpectedCharacter('f'));
431 }
432 },
433 _ => {
434 bail!(JsonErrorKind::UnexpectedCharacter('f'));
435 }
436 }
437 finished_value = Some(why);
438 } else {
439 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
440 }
441 }
442 b'n' => {
443 // wow it's a null — probably
444 let slice_rest = &input[pos..];
445 if slice_rest.starts_with(b"null") {
446 pos += 4;
447
448 // ok but we already pushed some! luckily wip has the method for us
449 wip = wip.pop_some_push_none().unwrap();
450 finished_value = Some(why);
451 } else {
452 bail!(JsonErrorKind::UnexpectedCharacter('n'));
453 }
454 }
455 c => {
456 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
457 }
458 }
459 }
460 Expect::Separator(separator) => match separator {
461 Separator::Colon => match c {
462 b':' => {
463 pos += 1;
464 }
465 _ => {
466 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
467 }
468 },
469 Separator::Comma(why) => match c {
470 b',' => {
471 pos += 1;
472 match why {
473 WhyComma::Array => {
474 stack.push(Expect::Separator(Separator::Comma(WhyComma::Array)));
475 stack.push(Expect::Value(WhyValue::ArrayElement));
476 wip = wip.push().unwrap();
477 }
478 WhyComma::Object => {
479 // looks like we're in for another round of object parsing
480 stack.push(Expect::Separator(Separator::Comma(WhyComma::Object)));
481 stack.push(Expect::Value(WhyValue::ObjectValue));
482 stack.push(Expect::Separator(Separator::Colon));
483 stack.push(Expect::Value(WhyValue::ObjectKey));
484 }
485 }
486 }
487 b'}' => match why {
488 WhyComma::Object => {
489 pos += 1;
490 finished_value = Some(WhyValue::ObjectValue);
491 }
492 _ => {
493 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
494 }
495 },
496 b']' => {
497 pos += 1;
498 match why {
499 WhyComma::Array => {
500 // we finished the array, neat
501 if frame_count > 1 {
502 wip = wip.pop().unwrap();
503 }
504 }
505 _ => {
506 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
507 }
508 }
509 }
510 _ => {
511 bail!(JsonErrorKind::UnexpectedCharacter(c as char));
512 }
513 },
514 },
515 }
516
517 if let Some(why) = finished_value {
518 trace!("Just finished value because of {:?}", why.green());
519 match why {
520 WhyValue::ObjectKey => {}
521 WhyValue::TopLevel | WhyValue::ObjectValue | WhyValue::ArrayElement => {
522 trace!("Shape before popping: {}", wip.shape());
523
524 let struct_has_default = wip
525 .shape()
526 .attributes
527 .iter()
528 .any(|attr| matches!(attr, ShapeAttribute::Default));
529 let mut has_missing_fields = false;
530
531 // Ensure all struct fields are set before popping
532 if let Def::Struct(sd) = wip.shape().def {
533 for i in 0..sd.fields.len() {
534 if !wip.is_field_set(i).unwrap() {
535 let missing_field: &'static str = sd.fields[i].name;
536 if struct_has_default {
537 has_missing_fields = true;
538 } else {
539 let field = sd.fields[i];
540 if let Some(attr) =
541 field.attributes.iter().find_map(|attr| match attr {
542 FieldAttribute::Default(d) => Some(d),
543 _ => None,
544 })
545 {
546 match attr {
547 Some(fun) => {
548 wip = wip
549 .field(i)
550 .unwrap()
551 .put_from_fn(*fun)
552 .unwrap()
553 .pop()
554 .unwrap();
555 }
556 None => {
557 wip = wip
558 .field(i)
559 .unwrap()
560 .put_default()
561 .unwrap()
562 .pop()
563 .unwrap();
564 }
565 }
566 } else {
567 bail!(JsonErrorKind::MissingField(missing_field));
568 }
569 }
570 }
571 }
572 }
573
574 if has_missing_fields {
575 trace!("struct has missing fields but we have default");
576 if !wip.shape().is(Characteristic::Default) {
577 todo!(
578 "Default struct has missing fields, the `default` impl but it does not implement Default"
579 )
580 }
581 let default_struct_val = Wip::alloc_shape(wip.shape())
582 .put_default()
583 .unwrap()
584 .build()
585 .unwrap();
586 let peek = default_struct_val.peek().into_struct().unwrap();
587
588 // For every missing field, take it from the peek and copy it into the wip
589 if let Def::Struct(sd) = wip.shape().def {
590 for i in 0..sd.fields.len() {
591 if !wip.is_field_set(i).unwrap() {
592 let field_value = peek.field(i).unwrap();
593 wip = wip
594 .field(i)
595 .unwrap()
596 .put_peek(field_value)
597 .unwrap()
598 .pop()
599 .unwrap();
600 }
601 }
602 }
603 }
604
605 if frame_count == 1 {
606 return Ok(wip.build().unwrap());
607 } else {
608 wip = wip.pop().unwrap();
609 }
610 }
611 }
612 }
613 }
614}
615
616fn skip_over_value(pos: &mut usize, input: &[u8]) -> Result<(), JsonErrorKind> {
617 let bytes = input;
618
619 // Helper for skipping whitespace
620 let skip_whitespace = |pos: &mut usize| {
621 while *pos < bytes.len() {
622 match bytes[*pos] {
623 b' ' | b'\t' | b'\n' | b'\r' => *pos += 1,
624 _ => break,
625 }
626 }
627 };
628
629 skip_whitespace(pos);
630
631 if *pos >= bytes.len() {
632 return Err(JsonErrorKind::UnexpectedEof(
633 "while skipping over value: input ended unexpectedly at root",
634 ));
635 }
636
637 match bytes[*pos] {
638 b'{' => {
639 // Skip a full object, recursively
640 *pos += 1;
641 skip_whitespace(pos);
642 if *pos < bytes.len() && bytes[*pos] == b'}' {
643 *pos += 1;
644 return Ok(());
645 }
646 loop {
647 // Skip key
648 skip_over_value(pos, input)?;
649 skip_whitespace(pos);
650 // Expect colon between key and value
651 if *pos >= bytes.len() || bytes[*pos] != b':' {
652 return Err(JsonErrorKind::UnexpectedEof(
653 "while skipping over value: object key with no colon or input ended",
654 ));
655 }
656 *pos += 1;
657 skip_whitespace(pos);
658 // Skip value
659 skip_over_value(pos, input)?;
660 skip_whitespace(pos);
661 if *pos >= bytes.len() {
662 return Err(JsonErrorKind::UnexpectedEof(
663 "while skipping over value: object value with EOF after",
664 ));
665 }
666 if bytes[*pos] == b'}' {
667 *pos += 1;
668 break;
669 } else if bytes[*pos] == b',' {
670 *pos += 1;
671 skip_whitespace(pos);
672 continue;
673 } else {
674 return Err(JsonErrorKind::UnexpectedCharacter(bytes[*pos] as char));
675 }
676 }
677 }
678 b'[' => {
679 // Skip a full array, recursively
680 *pos += 1;
681 skip_whitespace(pos);
682 if *pos < bytes.len() && bytes[*pos] == b']' {
683 *pos += 1;
684 return Ok(());
685 }
686 loop {
687 skip_over_value(pos, input)?;
688 skip_whitespace(pos);
689 if *pos >= bytes.len() {
690 return Err(JsonErrorKind::UnexpectedEof(
691 "while skipping over value: EOF inside array",
692 ));
693 }
694 if bytes[*pos] == b']' {
695 *pos += 1;
696 break;
697 } else if bytes[*pos] == b',' {
698 *pos += 1;
699 skip_whitespace(pos);
700 continue;
701 } else {
702 return Err(JsonErrorKind::UnexpectedCharacter(bytes[*pos] as char));
703 }
704 }
705 }
706 b'"' => {
707 // Skip a string, with escape processing
708 *pos += 1;
709 while *pos < bytes.len() {
710 match bytes[*pos] {
711 b'\\' => {
712 // Could have EOF after backslash
713 if *pos + 1 >= bytes.len() {
714 return Err(JsonErrorKind::UnexpectedEof(
715 "while skipping over value: EOF after backslash in string",
716 ));
717 }
718 *pos += 2; // Skip backslash and the next character (escaped)
719 }
720 b'"' => {
721 *pos += 1;
722 break;
723 }
724 _ => {
725 *pos += 1;
726 }
727 }
728 }
729 if *pos > bytes.len() {
730 return Err(JsonErrorKind::UnexpectedEof(
731 "while skipping over value: string ended unexpectedly",
732 ));
733 }
734 }
735 b't' => {
736 // Expect "true"
737 if bytes.len() >= *pos + 4 && &bytes[*pos..*pos + 4] == b"true" {
738 *pos += 4;
739 } else {
740 return Err(JsonErrorKind::UnexpectedCharacter('t'));
741 }
742 }
743 b'f' => {
744 // Expect "false"
745 if bytes.len() >= *pos + 5 && &bytes[*pos..*pos + 5] == b"false" {
746 *pos += 5;
747 } else {
748 return Err(JsonErrorKind::UnexpectedCharacter('f'));
749 }
750 }
751 b'n' => {
752 // Expect "null"
753 if bytes.len() >= *pos + 4 && &bytes[*pos..*pos + 4] == b"null" {
754 *pos += 4;
755 } else {
756 return Err(JsonErrorKind::UnexpectedCharacter('n'));
757 }
758 }
759 b'-' | b'0'..=b'9' => {
760 // Skip a number: -?\d+(\.\d+)?([eE][+-]?\d+)?
761 let start = *pos;
762 if bytes[*pos] == b'-' {
763 *pos += 1;
764 }
765 if *pos < bytes.len() && bytes[*pos] == b'0' {
766 *pos += 1;
767 } else {
768 while *pos < bytes.len() && (bytes[*pos] as char).is_ascii_digit() {
769 *pos += 1;
770 }
771 }
772 if *pos < bytes.len() && bytes[*pos] == b'.' {
773 *pos += 1;
774 let mut has_digit = false;
775 while *pos < bytes.len() && (bytes[*pos] as char).is_ascii_digit() {
776 *pos += 1;
777 has_digit = true;
778 }
779 if !has_digit {
780 return Err(JsonErrorKind::UnexpectedCharacter('.'));
781 }
782 }
783 if *pos < bytes.len() && (bytes[*pos] == b'e' || bytes[*pos] == b'E') {
784 *pos += 1;
785 if *pos < bytes.len() && (bytes[*pos] == b'+' || bytes[*pos] == b'-') {
786 *pos += 1;
787 }
788 let mut has_digit = false;
789 while *pos < bytes.len() && (bytes[*pos] as char).is_ascii_digit() {
790 *pos += 1;
791 has_digit = true;
792 }
793 if !has_digit {
794 return Err(JsonErrorKind::UnexpectedCharacter('e'));
795 }
796 }
797 if *pos == start {
798 return Err(JsonErrorKind::UnexpectedCharacter(bytes[start] as char));
799 }
800 }
801 _ => {
802 return Err(JsonErrorKind::UnexpectedCharacter(bytes[*pos] as char));
803 }
804 }
805 Ok(())
806}