Restore inner property name from map key in handleUnwrapped (#5126)#5193
Open
seonwooj0810 wants to merge 1 commit into
Open
Restore inner property name from map key in handleUnwrapped (#5126)#5193seonwooj0810 wants to merge 1 commit into
seonwooj0810 wants to merge 1 commit into
Conversation
…api#5126) `Schema.getName()` is `@JsonIgnore`, so a JSON-based clone of a Schema (e.g. `AnnotationsUtils.clone`) loses the name on every nested property schema while the surrounding properties-map key still carries the correct name. When `ModelResolver.handleUnwrapped` later forwards those property schemas into the outer model's `props` list, the subsequent `modelProps.put(prop.getName(), prop)` inserts a `null` key, which causes Jackson to fail serializing the OpenAPI document with `JsonMappingException: Null key for a Map not allowed in JSON`. Iterate the inner properties map by entry so the map key — the authoritative property name — can be restored on the property schema before it is added to `props`. The prefix/suffix branch is updated the same way: when the original prop's name has been lost by a prior clone, fall back to the entry key instead of producing a literal `prefix + "null" + suffix` name. Surfaces in practice with Spring HATEOAS `EntityModel<T>` (where `getContent()` is `@JsonUnwrapped`), but the fix is general — any path that round-trips an inner model through `AnnotationsUtils.clone` before unwrapping was affected.
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 #5126
Root cause
Schema.getName()is annotated@JsonIgnore, so it is not preserved across the JSON-based clones performed inside swagger-core (AnnotationsUtils.clone,Json.mapper().readValue(Json.pretty(schema), Schema.class), etc.). The clone restores the top-level name fromcloneName, but nested property schemas come back withname == null. The properties map keys are unaffected.ModelResolver.handleUnwrappedforwards those nested schemas into the outer model'spropslist withprops.addAll(innerModel.getProperties().values()). The subsequentfor (Schema prop : props) modelProps.put(prop.getName(), prop)then inserts anullkey, which causes Jackson to fail serializing the OpenAPI document with:In practice this is most reliably triggered by Spring HATEOAS
EntityModel<T>— wheregetContent()is@JsonUnwrappedand the model is round-tripped through clone before unwrapping — but the underlying invariant (map key vs transientnamefield can drift) is general.Change
handleUnwrappednow iteratesinnerModel.getProperties().entrySet()so the map key, which is the authoritative property name, can be restored on the schema when its transientnamehas been wiped by a clone.The prefix/suffix branch is updated the same way: instead of building
prefix + prop.getName() + suffix(which produces a literal"prefixnullsuffix"whenprop.getName()is null), it falls back to the entry key when the original name has been lost.Both branches now share the contract: a schema reached via
innerModel.getProperties()is named from its map-key.Test evidence
Ticket5126Testdirectly driveshandleUnwrappedwith an inner model whose property schemas have anullname — exactly the post-clone state described above — and asserts that the resulting property names come from the map keys, not from the clearednamefield. Two cases are covered: no prefix/suffix, and prefix/suffix.Without the change, both new tests fail:
noPrefixOrSuffix_restoresNameFromInnerPropertiesMapKey—Each unwrapped property must carry a non-null namewithPrefixAndSuffix_combinesMapKeyWhenInnerNameIsNull—expected [p_name_s, p_count_s] but got [p_null_s]With the change, the full
modules/swagger-coresuite (689 tests including all existing@JsonUnwrappedandModelResolverregression tests) passes.Verification done
gh pr list --repo swagger-api/swagger-core --search "5126"and the issue's timeline confirm no in-flight PR targets swagger-core for this bug (Fixes #3263 - strip null-key entries from component schema properties… springdoc/springdoc-openapi#3267 only adds a downstream workaround that strips null keys after swagger-core produces them; it does not fix the root cause)..javaonly.master(6e74355ca) that the buggyprops.addAll(innerModel.getProperties().values())is still present atModelResolver.handleUnwrappedline 1464.@JsonUnwrapped), and a fix proposal; both fix sites named there are covered.