Skip to content

fix: add mapBlankStringToNull option to JsonNullableModule - #126

Open
krangerich wants to merge 1 commit into
OpenAPITools:masterfrom
krangerich:fix/blank-string-deserializes-to-undefined
Open

fix: add mapBlankStringToNull option to JsonNullableModule#126
krangerich wants to merge 1 commit into
OpenAPITools:masterfrom
krangerich:fix/blank-string-deserializes-to-undefined

Conversation

@krangerich

@krangerich krangerich commented Feb 27, 2026

Copy link
Copy Markdown

Blank strings for non-String fields were silently deserialized as undefined() instead of of(null), breaking PATCH semantics. Default remains false for backwards compatibility.


Summary by cubic

Adds a module option to control how blank strings deserialize for non-String JsonNullable fields. When enabled, "" or " " become JsonNullable.of(null) instead of undefined, restoring correct PATCH semantics. Default stays false for backward compatibility.

  • New Features

    • JsonNullableModule.mapBlankStringToNull(boolean): if true, blank strings for non-String targets deserialize as JsonNullable.of(null). Strings are unaffected.
    • Configuration is passed through JsonNullableDeserializers and JsonNullableDeserializer; tests added.
  • Migration

    • To enable: register new JsonNullableModule().mapBlankStringToNull(true).

Written for commit 67a328e. Summary will update on new commits.

Blank strings for non-String fields were silently deserialized as undefined()
instead of of(null), breaking PATCH semantics. Default remains false for
backwards compatibility.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 6 files

@wing328

wing328 commented Mar 24, 2026

Copy link
Copy Markdown
Member

please resolve the merge conflicts when you've time

@KushalLukhi

Copy link
Copy Markdown

I reviewed the approach and it looks solid to me:\n\n- behavior change is opt-in () so backward compatibility is preserved\n- String targets remain unaffected\n- tests cover the new module behavior and existing paths\n\nI think this is a good fix for PATCH semantics. Once merge conflicts are resolved/rebased on latest , this looks ready for final maintainer pass.

@KushalLukhi

Copy link
Copy Markdown

Correction to my previous comment (formatting got mangled):

  • behavior change is opt-in via JsonNullableModule.mapBlankStringToNull(true), so backward compatibility is preserved
  • String targets remain unaffected
  • tests cover the new module behavior and existing paths

After merge conflicts are resolved against master, this looks ready for maintainer final pass.

@davidpavlovschi

Copy link
Copy Markdown

I read the diff. The design is the conservative one and several details are right that are easy to get wrong: the flag defaults to false so nothing changes for existing users; it is threaded through withResolved(...) so it survives contextualization (a genuine trap in a ReferenceTypeDeserializer — a flag that is dropped there silently reverts on any contextualized property); the javadoc explains the PATCH motivation rather than just restating the parameter name; and testJsonNullableFromBlankWithMapBlankStringToNull covers whitespace-only input, not just "".

The blocker is bigger than the "resolve the merge conflicts" comment suggests, and I do not think it has been stated on this PR yet.

The three files this PR patches no longer exist on master. #117 ("Implement Jackson3 support and maintain Jackson2 support", merged) renamed them and added a parallel set. Current src/main/java/org/openapitools/jackson/nullable/ contains:

  • JsonNullableJackson2Deserializer / JsonNullableJackson2Deserializers (was JsonNullableDeserializer / JsonNullableDeserializers)
  • JsonNullableJackson3Deserializer / JsonNullableJackson3Deserializers
  • JsonNullableModule and JsonNullableJackson3Module

So this is not a textual conflict resolution, it is a port. Concretely, the rebase has to:

  1. Add the mapBlankStringToNull field + constructor parameter + withResolved propagation to both JsonNullableJackson2Deserializer and JsonNullableJackson3Deserializer. The Jackson 3 one uses p.currentToken() / p.getString() and throws JacksonException rather than IOException, so it is not a copy-paste.
  2. Add the constructor argument to both ...Jackson2Deserializers and ...Jackson3Deserializers.
  3. Add the setter to both JsonNullableModule and JsonNullableJackson3Module.

If step 1 or 3 is skipped for the Jackson 3 half, mapBlankStringToNull(true) compiles, is accepted, and silently does nothing for Jackson 3 users — the worst failure mode for an opt-in flag. Please make sure the test additions run against both processors; the test base has since moved to a parameterized JsonProcessor abstraction that makes this straightforward.

Two smaller points for the rebase:

  • The fluent setter returns JsonNullableModule, so unless JsonNullableJackson3Module gets the same method the two entry points diverge in API surface. Worth mirroring the signature exactly.
  • mapBlankStringToNull is read in setupModule, which runs at registerModule(...) time. Calling the setter after registration silently has no effect. Either document that on the setter or make the field final via a constructor argument.

Finally, note this competes directly with #152, which makes the same behaviour change unconditionally and is already rebased on the post-#117 layout. The opt-in approach here is the backward-compatible one and I would argue for it on the 0.2.x line, but a maintainer needs to pick one before either author spends more time.

I did not run this branch's tests: it targets classes that no longer exist on master, so it cannot compile there. Everything above is from reading the diff against the current tree.

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.

4 participants