| 1 | import x.json2 |
| 2 | |
| 3 | // Test for issue #26503: Decoder incorrectly reads nested object fields |
| 4 | // When a required field appears after unmatched fields with nested objects/arrays, |
| 5 | // the decoder was incorrectly picking up values from nested structures. |
| 6 | |
| 7 | struct Foo { |
| 8 | id string @[required] |
| 9 | title string @[required] |
| 10 | } |
| 11 | |
| 12 | fn test_decode_with_nested_objects_field_order() { |
| 13 | // Test case 1: required fields appear before the nested array |
| 14 | s1 := '{"id":"sss","title":"ttt","thumb":[{ "url":"i1.jpg","id":"000"}]}' |
| 15 | f1 := json2.decode[Foo](s1)! |
| 16 | |
| 17 | assert f1.id == 'sss', 'f1.id should be "sss" but got "${f1.id}"' |
| 18 | assert f1.title == 'ttt', 'f1.title should be "ttt" but got "${f1.title}"' |
| 19 | |
| 20 | // Test case 2: required fields appear after the nested array |
| 21 | s2 := '{"title":"ttt","thumb":[{ "url":"i1.jpg","id":"000"}],"id":"sss"}' |
| 22 | f2 := json2.decode[Foo](s2)! |
| 23 | |
| 24 | assert f2.id == 'sss', 'f2.id should be "sss" but got "${f2.id}"' |
| 25 | assert f2.title == 'ttt', 'f2.title should be "ttt" but got "${f2.title}"' |
| 26 | |
| 27 | // Test case 3: nested array appears between required fields |
| 28 | s3 := '{"id":"sss","thumb":[{ "url":"i1.jpg","id":"000"}],"title":"ttt"}' |
| 29 | f3 := json2.decode[Foo](s3)! |
| 30 | |
| 31 | assert f3.id == 'sss', 'f3.id should be "sss" but got "${f3.id}"' |
| 32 | assert f3.title == 'ttt', 'f3.title should be "ttt" but got "${f3.title}"' |
| 33 | } |
| 34 | |
| 35 | fn test_decode_with_deeply_nested_objects() { |
| 36 | // Test with deeply nested structures to ensure complete skipping |
| 37 | s := '{"id":"outer","data":{"nested":{"deep":{"id":"inner","title":"inner_title"}}},"title":"outer_title"}' |
| 38 | f := json2.decode[Foo](s)! |
| 39 | |
| 40 | assert f.id == 'outer', 'id should be "outer" but got "${f.id}"' |
| 41 | assert f.title == 'outer_title', 'title should be "outer_title" but got "${f.title}"' |
| 42 | } |
| 43 | |
| 44 | fn test_decode_with_multiple_nested_arrays() { |
| 45 | // Test with multiple nested arrays to ensure complete skipping |
| 46 | s := '{"id":"correct","items":[{"id":"a"},{"id":"b"}],"more":[{"id":"c"}],"title":"correct_title"}' |
| 47 | f := json2.decode[Foo](s)! |
| 48 | |
| 49 | assert f.id == 'correct', 'id should be "correct" but got "${f.id}"' |
| 50 | assert f.title == 'correct_title', 'title should be "correct_title" but got "${f.title}"' |
| 51 | } |
| 52 | |
| 53 | struct BarWithOptional { |
| 54 | id string @[required] |
| 55 | title ?string |
| 56 | extra string |
| 57 | } |
| 58 | |
| 59 | fn test_decode_optional_fields_with_nested() { |
| 60 | // Test optional fields work correctly with nested structures |
| 61 | s := '{"id":"main","nested":{"title":"nested_title"},"extra":"extra_value"}' |
| 62 | b := json2.decode[BarWithOptional](s)! |
| 63 | |
| 64 | assert b.id == 'main', 'id should be "main" but got "${b.id}"' |
| 65 | assert b.extra == 'extra_value', 'extra should be "extra_value" but got "${b.extra}"' |
| 66 | if title := b.title { |
| 67 | assert false, 'title should be none but got "${title}"' |
| 68 | } |
| 69 | } |
| 70 | |