Skip to content

fix(dart): preserve null for optional list properties#2971

Merged
schani merged 1 commit into
masterfrom
agent/fix-issue-2656
Jul 20, 2026
Merged

fix(dart): preserve null for optional list properties#2971
schani merged 1 commit into
masterfrom
agent/fix-issue-2656

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

The bug

For Dart output with null safety (the default), an optional List<T>?
property round-tripped null as [] instead of preserving null:

factory Coordinate.fromJson(Map<String, dynamic> json) => Coordinate(
    onlyFor: json["onlyFor"] == null ? [] : List<String>.from(json["onlyFor"]!.map((x) => x)),
);

Map<String, dynamic> toJson() => {
    "onlyFor": onlyFor == null ? [] : List<dynamic>.from(onlyFor!.map((x) => x)),
};

Even though the field is declared nullable (List<String>?), the
(de)serialization logic silently collapsed null into [], losing the
null-vs-empty-list distinction that some APIs rely on semantically (e.g. a
consumer checking onlyFor == null to mean "not applicable" vs.
onlyFor == [] meaning "applicable to nothing").

Root cause

mapList in packages/quicktype-core/src/language/Dart/DartRenderer.ts
unconditionally substituted [] for a null list in both fromJson and
toJson, rather than following the same pattern mapClass already uses a
few lines below it for nullable class-typed properties
(x == null ? null : X.fromJson(x)).

The fix

One-line change: mapList now emits list == null ? null : ... instead
of list == null ? [] : ..., for both the deserialization and
serialization directions:

factory Coordinate.fromJson(Map<String, dynamic> json) => Coordinate(
    onlyFor: json["onlyFor"] == null ? null : List<String>.from(json["onlyFor"]!.map((x) => x)),
);

Map<String, dynamic> toJson() => {
    "onlyFor": onlyFor == null ? null : List<dynamic>.from(onlyFor!.map((x) => x)),
};

Scope is intentionally limited to List (arrays), matching the issue
title/report; Map handling in Dart wasn't touched.

Test coverage

test/languages.ts's Dart config already skipped
test/inputs/schema/optional-const-ref.schema with a comment describing
this exact bug:

/* Absent optional lists don't round-trip: the generated fromJson/toJson
   turn them into [], so the output no longer matches the input */
"optional-const-ref.schema",

That schema has an optional array property (coordinates) and a sample
(optional-const-ref.2.json) where the key is entirely absent. Dart's
fixture comparison already runs with allowMissingNull: true, which treats
an explicit null value as equivalent to an absent key
(test/lib/deepEquals.ts) — so once mapList emits null instead of
[], this fixture round-trips correctly. The skip entry (and its
explanatory comment) has been removed, re-enabling this fixture as
regression coverage.

Verification

  • npm run build passes.
  • npm run test:unit — 163 tests pass.
  • Manually re-generated Dart from the issue's repro schema and from
    optional-const-ref.schema; confirmed null is now preserved through
    both fromJson and toJson instead of becoming [].
  • Could not execute the Dart fixture end-to-end (FIXTURE=dart script/test)
    locally because the dart toolchain isn't available in this sandbox; CI
    will run the re-enabled optional-const-ref.schema fixture.

Fixes #2656

🤖 Generated with Claude Code

Dart's fromJson/toJson for nullable List properties collapsed null
into an empty array ("x == null ? [] : ..."), even though the field
is declared nullable (List<T>?). This lost the null-vs-empty-list
distinction that some APIs rely on. mapList now emits
"x == null ? null : ..." for both directions, matching the pattern
already used by mapClass for nullable class-typed properties.

Also re-enables the Dart optional-const-ref.schema fixture, which was
previously skipped specifically because of this bug (absent optional
list properties didn't round-trip).

Fixes #2656

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
@schani
schani merged commit 0dee142 into master Jul 20, 2026
28 checks passed
@schani
schani deleted the agent/fix-issue-2656 branch July 20, 2026 22:03
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.

[BUG]: (Dart) Null is better than empty array for null/undefined list-type properties

1 participant