fix(java): emit guarded undiscriminated-union members before all-optional ones in deserializer#15859
Conversation
…bers before all-optional ones All-optional object types (no required fields) were greedily consuming payloads intended for more specific union members that have at least one required field. The deserializer now stably sorts members so those with required key guards are emitted first. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
Description
Linear ticket: Closes
Fix incorrect deserialization of Java undiscriminated unions where one variant has all-optional fields. Because Jackson's
@JsonIgnoreProperties(ignoreUnknown = true)silently accepts any JSON object when every field is optional, an all-optional variant could greedily match a payload intended for a more specific sibling that requires certain keys.The generated deserializer in
UndiscriminatedUnionGeneratornow stably partitions non-primitive members so that variants with at least one required wire key (a "key guard") are attempted first, and unguarded (all-optional) variants are tried last. This preserves prior ordering within each partition while ensuring the more specific match wins.Concrete failure mode this fixes: a union containing
Check(requiresachHolder) andPayMethodCloud(all optional) previously deserialized{"achHolder": "..."}asPayMethodCloudinstead ofCheck.Changes Made
generators/java/generator-utils/src/main/java/com/fern/java/generators/UndiscriminatedUnionGenerator.java: filter out primitives/boxed primitives, thenList#sortnon-primitive members so those with required wire keys (getRequiredWireKeys(...)non-empty) come before those without, before emitting theirtry { ... } catchblocks.generators/java/sdk/changes/unreleased/fix-undiscriminated-union-all-optional-ordering.yml: add afixchangelog entry describing the deserialization fix.Testing
Link to Devin session: https://app.devin.ai/sessions/2e4e587dd160400390839880a5434932
Requested by: @iamnamananand996