Skip to content

fix: preserve Kotlin pure dictionary models - #177

Merged
adamcavendish merged 1 commit into
mainfrom
adamcavendish/kotlin-dictionary-models
Nov 12, 2026
Merged

adamcavendish merged 1 commit into
mainfrom
adamcavendish/kotlin-dictionary-models

Conversation

@adamcavendish

@adamcavendish adamcavendish commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Generate usable Kotlin models for named pure dictionaries and retain their flat
JSON entries through model and HTTP conversion.

Problem

A named object with only additionalProperties reaches the Kotlin emitter as an
object with no named fields. The emitter previously ignored its dictionary value
type and attempted an empty data class, which sigil-stitch correctly rejects.
Even the single-schema Labels string dictionary therefore prevents generation.

Using a Map subclass does not solve the full problem: Gson selects its built-in
map codec before the annotated adapter, discarding null entries and bypassing the
dictionary's object-root handling.

Behavior

Pure dictionary models now expose typed mutable additionalProperties storage,
defaulting to an empty map. A private Gson adapter flattens that storage directly
to JSON object keys; no additionalProperties wrapper is added on the wire.

val labels = Labels()
labels.additionalProperties["keep_key"] = "value"
val json = Gson().toJson(labels) // {"keep_key":"value"}

Keys remain verbatim, including keys resembling renamed fields or the storage
property itself. Values retain scalar, generated-model, nullable and container
types. Opaque dictionaries use JsonElement values, preserving arbitrary trees,
null entries and large numeric literals. Nested containers preserve their own
entries while ordinary model-valued leaves keep their normal Gson codecs.

Entries decode directly from the original JSON reader through their declared
native codecs. Fractional or overflowing integer values retain native decode
errors instead of being silently truncated or wrapped by tree-reader conversions.
Authored model and dictionary types use their configured model-package identities,
including inside nested containers, so Gson helpers such as TypeToken,
JsonElement, JsonNull and Gson cannot replace same-named generated models.

Aliases, recursive values, containing models and union boundaries use the same
dictionary representation. HTTP requests, successful responses and lazy typed
error bodies retain dictionary payloads. Root JSON null stays Kotlin null;
non-object roots fail instead of silently becoming empty dictionaries. Writers
restore the surrounding null policy even when serialization fails.

Boundaries

This is the pure Kotlin dictionary repair after merged union #176. The new path
applies only to objects with no named fields and explicit additional-property
value facts. Ordinary and mixed objects, tagged codecs, aliases, enum declarations,
shared IR, other-language generators and generated dependencies are unchanged.
Mixed-object additional-property preservation and multi-member allOf wire-shape
redesign remain separate. Value decoding uses the retained IR representations and
existing native codecs; this is not new JSON Schema validation or a shared-lowerer
redesign. Generating the larger wire-preservation fixture does not claim that its
separate mixed-object and composition defects are fixed.

@codecov

codecov Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.63314% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/generators/kotlin/okhttp/dictionary.rs 96.69% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

- Generate nonempty data classes for named objects whose only declared content
  is additional properties instead of failing empty-data-class validation.
- Expose typed mutable additionalProperties storage with an empty default while
  reading and writing flat JSON objects without a synthetic storage wrapper.
- Keep dictionary storage outside Map inheritance so Gson honors its adapter
  instead of selecting its built-in map codec and silently dropping null entries.
- Preserve verbatim keys, nullable values, raw JSON trees, numeric precision and
  nested containers while delegating ordinary model values to their native codecs.
- Decode entries from the original JSON reader so native integer errors propagate
  instead of being silently truncated or wrapped by Gson's tree-reader conversions.
- Qualify authored model and dictionary types within nested containers so Gson
  helper imports cannot shadow their declared identities or break compilation.
- Restore the writer's null policy even on failure and keep ordinary model null
  omission, aliases, recursive values and union member boundaries intact.
- Retain HTTP request, response and typed-error dictionary payloads with native
  consumers on unchanged generated projects and dependency manifests.
- Limit the repair to pure Kotlin dictionaries; mixed-object extension storage,
  shared IR, other languages and allOf wire-shape redesign remain separate.
@adamcavendish
adamcavendish force-pushed the adamcavendish/kotlin-dictionary-models branch from edc0ea3 to a1e8dc3 Compare November 12, 2026 10:16
@adamcavendish
adamcavendish merged commit e3f7daa into main Nov 12, 2026
31 checks passed
@adamcavendish
adamcavendish deleted the adamcavendish/kotlin-dictionary-models branch November 12, 2026 10:21
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