Skip to content

Add opt-in nil_fields setting to encode missing values as null - #201

Merged
andreashasse merged 4 commits into
mainfrom
andreashasse/nil-fields-as-null
Oct 30, 2026
Merged

andreashasse merged 4 commits into
mainfrom
andreashasse/nil-fields-as-null

Conversation

@andreashasse

Copy link
Copy Markdown
Owner

Stacked on the schema fixes PR. The base is andreashasse/schema-and-error-fixes, and GitHub retargets this PR to main once that PR merges.

A field whose type can be missing (T | undefined, or Elixir T | nil) is left out when it is encoded, and its schema does not allow null. The new application setting {spectra, [{nil_fields, null}]} (in Elixir, config :spectra, :nil_fields, :null) does three things:

  • It encodes a missing value as JSON null.
  • It marks the field required in the schema, as type: [T, "null"] or anyOf: [..., {type: null}].
  • It makes typed map value schemas (additionalProperties) allow null.

The encoder and the schema generator read the setting from the same #sp_config{} per call, so they cannot disagree. Fields whose type cannot be missing are unaffected. With the default, omit, the output is exactly as before. An invalid value raises {invalid_config, nil_fields, Value}.

Tests cover both settings, records and :=/=> maps, typed map values, decoding null, and encoded output validated against the generated schema (with jesse and openapi-spec-validator). make test passes (803 tests), and so do dialyzer, hank, xref, the format check and proper.

🤖 Generated with Claude Code

@andreashasse
andreashasse changed the base branch from andreashasse/schema-and-error-fixes to main October 30, 2026 22:22
andreashasse and others added 3 commits October 30, 2026 23:22
A field whose type can be missing (T | undefined, T | nil) is left out
of the JSON when it holds the missing value. Some clients want every
field present. The new application setting
{spectra, [{nil_fields, null}]} writes such fields as JSON null instead.

The setting is read once per call into #sp_config{}, which both the
encoder and the schema generator use, so they cannot disagree. With
null, the field is listed in required and its schema allows null
(type: [T, "null"] for a plain type, anyOf otherwise). Optional (=>)
map keys absent from the data stay absent and stay out of required.
Typed map values that are missing also become null. The default, omit,
leaves output unchanged; an unknown value raises
{invalid_config, nil_fields, Value}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With nil_fields = null the encoder writes a missing typed map value as
null, but additionalProperties was built without the missing member and
rejected it. Build the value schema through field_schema so it allows
null, as record and map fields do. Unchanged under omit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@andreashasse
andreashasse force-pushed the andreashasse/nil-fields-as-null branch from 4225140 to 9997e1a Compare October 30, 2026 22:23
Give add_missing_field/3 an unconstrained key type so a binary key list
stays binary, and skip nil_fields_test in eqwalizer like the other
test modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@andreashasse
andreashasse merged commit c6994fe into main Oct 30, 2026
3 checks passed
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