Skip to content

v2.11.0 - fix: Json decode<Map> cast nested lists to List<Map>; dart_style 3.1.13, analyzer 14 - #56

Merged
gmpassos merged 5 commits into
masterfrom
fix/json-decode-nested-lists
Oct 13, 2026
Merged

gmpassos merged 5 commits into
masterfrom
fix/json-decode-nested-lists

Conversation

@gmpassos

@gmpassos gmpassos commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Fix: nested JSON lists decoded as lazily cast List<Map>

JsonDecoder.decode<Map> / decodeAsync<Map> (and decodeFromBytes*, fromJson*, fromJsonMap*, JsonCodec.decode*) decoded the values of an untyped Map with the Map TypeInfo itself. A nested JSON list therefore took Map as its element type and was wrapped by List.cast<Map>(): decoding succeeded, then reading (or adding) a non-Map element threw a TypeError far from the decode call. An untyped List/Set/Iterable had the same defect for its elements.

A collection type without arguments (Map, List, Set, Iterable) now decodes its values/elements as dynamic, so the result matches dart:convert's jsonDecode. Collections with arguments (Map<String, int>, List<User>) and entity types are unchanged.

Review follow-up: the first version of the fix also sent collections with arguments to dynamic, dropping Map<String, int> / Map<String, Entity> / List<Entity> decoding. Fixed in 5235232 with one shared rule (_untypedCollectionAsDynamic), plus regression tests (sync and async) that fail on the previous commit.

Behavior changes (documented in the CHANGELOG): nested lists under untyped collections are now List<dynamic> rather than List<Map>/List<List> views; type decoders keyed on Map/List no longer see the scalars nested under an untyped collection.

Tests

For the fix (JsonDecoder untyped collections groups): every decode entry point compared against jsonDecode — scalars, nulls, mixed types, map-first vs scalar-first, nested/empty lists, date-like strings, top-level lists, untyped Set/Iterable, a non-standard decoder (no raw fast path), JsonCodec, encode→decode round trips, 200-level nesting, a 1000-element mixed list, unusual keys, writes to decoded lists. The fix-specific tests fail on the old code.

Entity integration: new TestPayloadWithReflection (Map, Map<String, dynamic>, List<Map>, List<List>, List, dynamic fields) through every decode entry point, generated $fromJson/$fromJsonEncoded, round trips and lists of entities. Reflection records these fields with explicit dynamic arguments, so they were not affected by the bug; these tests guard that path.

General coverage:

  • reflection_factory_json_paths_test.dart: async decoding with Future inputs/values, decoder/encoder hooks and their fallbacks, scalar parsing, callToJson, isStandard, MimeTypeResolverExtension.
  • reflection_factory_paths_test.dart: MethodInvocation, optional positional parameters, JsonField accessors, EnumReflection, static field setters, constructor invocation errors.

Line coverage: reflection_factory_json.dart 86.9% → 95.9%, reflection_factory_base.dart 83.1% → 89.5%.

Dependencies

  • dart_style: >=3.1.10 <3.1.11 → >=3.1.13 <3.1.14. Dart 3.13.2 bundles 3.1.13 (3.13.0–3.13.1 bundle 3.1.10), so the old pin no longer matched dart format.
  • environment.sdk: >=3.13.0 → >=3.13.2, keeping the pin matched to the consumer's SDK (as in 2.9.0). Narrower SDK range → minor bump.
  • analyzer: ^13.0.0 → ^14.5.0 (allowed by dart_style 3.1.13); no code changes needed.
  • Lower bounds raised to resolved versions: build, mime, pub_semver, yaml, build_runner, build_test, pubspec_parse, data_serializer, test.

Regenerated code is unchanged apart from the version string (and the new test entity).

Verification

  • dart analyze: no issues
  • dart format --set-exit-if-changed lib test example: clean
  • dart test: 520 passed (VM); changed test files also pass on Chrome

🤖 Generated with Claude Code

gmpassos and others added 2 commits October 13, 2026 04:25
… (v2.10.2)

The values of an untyped Map were decoded with the Map TypeInfo itself, so
a nested JSON list took Map as its element type and was wrapped by
List.cast<Map>(): decoding succeeded, and reading (or adding) a non-Map
element threw later. An untyped List had the same defect for its elements.

A collection TypeInfo without arguments now decodes its values/elements as
dynamic, matching dart:convert's jsonDecode. Typed collections and entity
types are unchanged.

Adds a "JsonDecoder untyped collections" test group covering every decode
entry point (sync, async, bytes, fromJson*, fromJsonMap*) over scalar, mixed,
null, empty, nested and deep lists, compared against jsonDecode.

Also updates dependencies within the current constraints (analyzer 14 is
blocked by the dart_style 3.1.10 pin).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dart 3.13.2 bundles dart_style 3.1.13 (3.13.0-3.13.1 bundle 3.1.10), so the
3.1.10 pin no longer matched `dart format` on current SDKs. Move the pin and
raise the SDK floor with it, as in 2.9.0; the narrower SDK range makes this
a minor bump (2.10.2 -> 2.11.0, unreleased).

dart_style 3.1.13 accepts analyzer 14, so move to analyzer ^14.5.0, plus
build_runner ^2.16.2 and build_test ^3.5.21. Generated code is unchanged
apart from the version string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.56%. Comparing base (72f80a4) to head (4902e34).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #56      +/-   ##
==========================================
+ Coverage   89.56%   92.56%   +2.99%     
==========================================
  Files          14       14              
  Lines        6423     6428       +5     
==========================================
+ Hits         5753     5950     +197     
+ Misses        670      478     -192     
Flag Coverage Δ
unittests 92.56% <100.00%> (+2.99%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

gmpassos and others added 3 commits October 13, 2026 05:08
…N/reflection coverage

Decode fix:
- Untyped Set/Iterable, a non-standard decoder (no raw fast path),
  JsonCodec, encode/decode round trips, 200-level nesting, a 1000-element
  mixed list, unusual keys/strings.
- Entity integration: new TestPayloadWithReflection with Map, Map<String,
  dynamic>, List<Map>, List<List>, List and dynamic fields, through every
  decode entry point, generated $fromJson/$fromJsonEncoded, round trips and
  lists of entities. Reflection records these fields with explicit dynamic
  arguments, so they were not affected by the bug; the tests guard them.

General:
- reflection_factory_json_paths_test.dart: async decoding with Future
  inputs/values, decoder/encoder hooks and fallbacks, scalar parsing,
  callToJson, isStandard, MimeTypeResolverExtension.
- reflection_factory_paths_test.dart: MethodInvocation, optional positional
  parameters, JsonField accessors, EnumReflection, static field setters,
  constructor invocation errors.

Line coverage: reflection_factory_json.dart 86.9% -> 95.9%,
reflection_factory_base.dart 83.1% -> 89.5%. 516 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#56)

The nested-list fix sent every collection TypeInfo to `dynamic`, including
ones with arguments, so `Map<String, int>`, `Map<String, Entity>` and
`List<Entity>` element/value types were dropped (entities stayed raw maps).
Only a collection *without* arguments falls back to `dynamic` now, through
one shared `_untypedCollectionAsDynamic` used by both decode sites.

Adds regression tests (sync and async) for the three cases; they fail on
the previous commit.

CHANGELOG: document the behavior changes (List<dynamic> instead of
List<Map>/List<List> views; type decoders keyed on Map/List no longer see
nested scalars), the analyzer major-version note, and correct the generated
code wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Values keep the iterable TypeInfo (only list values become entities). Using
its element type instead would turn map values into entities and scalars
into null (1) or entities built from a string ("x"). The suite passed with
that variant; this test fails on it and passes on master and on this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gmpassos
gmpassos merged commit 77164c6 into master Oct 13, 2026
3 checks passed
@gmpassos
gmpassos deleted the fix/json-decode-nested-lists branch October 13, 2026 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant