fix: JSON converter mapper caches, record component serde annotations, and declared-type writes - #448
Merged
Merged
Conversation
…, and declared-type writes The Jackson converters key their mapper cache on the field's shape: the sealed types collected from the full generic type and, when a custom serializer or deserializer is present, the raw field type it is registered for. Sealed interfaces reached through container types (List, Set, Map, arrays) get their permitted subtypes registered, so the discriminator resolves for container elements. The kotlinx cache is keyed on the Json flags alone, bounding it at one mapper per flag combination. RecordField metadata now includes annotations that Java propagates to the backing field, accessor or constructor parameter; an annotation only reaches the record component itself when its targets include RECORD_COMPONENT, which third-party annotations rarely declare. JsonSerialize/JsonDeserialize on a Json record component are therefore honored. Json fields serialize with the declared field type instead of the value's erased runtime type, so a polymorphic value writes the discriminator that reading the column expects. Fixes #410
storm-jackson2's suite gains a Kotlin interop test compiled from src/test/kotlin, with storm-kotlin and jackson-module-kotlin on the test class path: the configuration every Kotlin application has. It covers the language seams the Java suite cannot reach: serde annotations on Kotlin constructor properties, Kotlin sealed hierarchies top-level and as container elements, one serializer class shared by fields of different Kotlin types, and Java sealed hierarchies enumerated through Kotlin reflection. The existing Java tests now also run with the Kotlin provider active, which delegates Java records to the default reflection; storm-jackson3's suite keeps covering the pure-Java configuration.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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.
Fixes #410.
Mapper cache keying (the issue's three points)
@Json List<Shape>— gets its permitted subtypes registered and the discriminator resolves for container elements. Previously only a top-level sealed field triggered registration.@JsonSerialize/@JsonDeserializeis present, the raw field type it is registered against joins the cache key. Two fields of different types sharing one serializer class each get a mapper serving their own type; previously the second field silently reused the first field's mapper and bypassed its serializer. Fields without custom serde keep sharing the plain mapper.@Jsonflags alone —buildJsonnever read the sealed component — bounding the map at one mapper per flag combination (four) and dropping theClassretention.Two further bugs surfaced by the regression tests
@JsonSerialize/@JsonDeserializedo not declare theRECORD_COMPONENTtarget, so javac propagates them to the backing field, accessor and constructor parameter — andRecordComponent.getAnnotations()returns nothing. The converter's custom-serde branch was unreachable, and the existing round-trip test could not detect that: a bypassed serializer plus a bypassed deserializer still round-trips.DefaultORMReflectionImplnow folds component, backing field, accessor and constructor parameter annotations together (equal instances propagated to several sites collapse, keeping single-instance lookups unambiguous), matching the parameter + property merge storm-kotlin already does.toDatabaseserialized with the value's erased runtime type, so@Json List<Shape>wrote elements without@typeand the same converter could not read them back. Serialization now uses anObjectWriterfor the declaredTypeReference, aligning the write side with the read side (and with the kotlinx converter, which always serialized via the declared type's serializer).Behavior note: a field declared as a non-polymorphic supertype holding a subclass value now writes the declared properties only. The previous output wrote the subclass properties, which reading either dropped silently or rejected under
@Json(failOnUnknown = true); subtype fidelity is expressed with@JsonTypeInfo, which now works in containers too.Verification
Listand inMap.Object-declared cases.Kotlin interop coverage
storm-jackson2's suite now includes a Kotlin interop test compiled from
src/test/kotlin, with storm-kotlin andjackson-module-kotlinon the test class path: the configuration every Kotlin application has. It pins the language seams the Java suite cannot reach — serde annotations on Kotlin constructor properties (which land on the constructor parameter, where storm-kotlin's parameter + property merge finds them), Kotlin sealed hierarchies top-level and as container elements (Kotlin emits the JVMPermittedSubclassesattribute, so the sealed-type walk sees them), one serializer class shared by fields of different Kotlin types, and Java sealed hierarchies enumerated throughKClass.sealedSubclasses. The existing Java tests in the module now also run with the Kotlin reflection provider active, which delegates Java records to the default implementation; storm-jackson3's suite keeps covering the pure-Java configuration. storm-jackson2 stands at 116 tests, all green.