fix(dart): preserve null for optional list properties#2971
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
For Dart output with null safety (the default), an optional
List<T>?property round-tripped
nullas[]instead of preservingnull:Even though the field is declared nullable (
List<String>?), the(de)serialization logic silently collapsed
nullinto[], losing thenull-vs-empty-list distinction that some APIs rely on semantically (e.g. a
consumer checking
onlyFor == nullto mean "not applicable" vs.onlyFor == []meaning "applicable to nothing").Root cause
mapListinpackages/quicktype-core/src/language/Dart/DartRenderer.tsunconditionally substituted
[]for a null list in bothfromJsonandtoJson, rather than following the same patternmapClassalready uses afew lines below it for nullable class-typed properties
(
x == null ? null : X.fromJson(x)).The fix
One-line change:
mapListnow emitslist == null ? null : ...insteadof
list == null ? [] : ..., for both the deserialization andserialization directions:
Scope is intentionally limited to
List(arrays), matching the issuetitle/report;
Maphandling in Dart wasn't touched.Test coverage
test/languages.ts's Dart config already skippedtest/inputs/schema/optional-const-ref.schemawith a comment describingthis exact bug:
That schema has an optional array property (
coordinates) and a sample(
optional-const-ref.2.json) where the key is entirely absent. Dart'sfixture comparison already runs with
allowMissingNull: true, which treatsan explicit
nullvalue as equivalent to an absent key(
test/lib/deepEquals.ts) — so oncemapListemitsnullinstead of[], this fixture round-trips correctly. The skip entry (and itsexplanatory comment) has been removed, re-enabling this fixture as
regression coverage.
Verification
npm run buildpasses.npm run test:unit— 163 tests pass.optional-const-ref.schema; confirmednullis now preserved throughboth
fromJsonandtoJsoninstead of becoming[].FIXTURE=dart script/test)locally because the
darttoolchain isn't available in this sandbox; CIwill run the re-enabled
optional-const-ref.schemafixture.Fixes #2656
🤖 Generated with Claude Code