feat: allowSuppressNulls option to omit null values in serialization
Fixes #51
What
Adds an opt-in allowSuppressNulls(boolean) flag to JsonNullableModule and JsonNullableJackson3Module:
new JsonNullableModule().allowSuppressNulls(true)
With the flag off (the default) behavior is byte-for-byte identical to today, including JsonNullable.of(null) being written as an explicit null even under NON_NULL / NON_ABSENT inclusion. With the flag on, JsonNullable-typed bean properties holding a null value (JsonNullable.of(null), ofNull(), undefined(), or a plain null reference) are omitted from the output entirely.
Why an explicit flag on the module
- The distinction between property absent and property present with
nullis meaningful for PATCH-style payloads, so this cannot become the default for everyone (see the discussion in #51 — several users rely on today's semantics, and the latest suggestion there was exactlynew JsonNullableModule().allowSuppressNulls(true)). The flag keeps the absent-vs-null distinction available while letting response bodies drop the bloat. - It deliberately does not piggyback on Jackson's
Include.NON_NULLsemantics: the requested output ("no JSONnullfor a cleared value") is not expressible with the existingJsonIncludecontract forJsonNullable, sinceof(null)is a present-but-null reference value, not anullproperty. - Naming follows the API proposed in #51 and the module's existing fluent option style (
mapBlankStringToNull(boolean)).
Where the nulls are dropped
BeanPropertyWriter writes the field name before invoking the value serializer, so the JsonNullableJackson2Serializer/JsonNullableJackson3Serializer cannot un-write a field mid-object — that is also why the serializer forces suppressNulls = false today. The suppression therefore happens in the JsonNullable bean property writers (JsonNullableJackson2BeanPropertyWriter, JsonNullableJackson3BeanPropertyWriter and both unwrapping variants), which is exactly where JsonNullable.undefined() was already omitted before this change:
- flag off: skip only
undefined()(and plainnullwhen inclusion suppresses nulls) — unchanged; - flag on: additionally skip when the value is
nullor present-but-null (isNull()), regardless of the configured inclusion.
Consequences, documented in the README/javadoc:
- Root values, array elements and
Mapvalues cannot skip entries mid-stream and still serialize as JSONnull. - Non-
JsonNullableproperties are not affected. - Deserialization is untouched; a suppressed field reads back as
undefined().
Tests
JsonNullableSuppressNullsTest (parameterized over Jackson 2 / Jackson 3, mirroring the existing suite):
- flag off: default,
NON_NULLand non-bean output identical to current behavior; - flag on:
of(null)/ofNull()/ plainnullomitted under default,NON_NULL,NON_ABSENT,NON_EMPTYinclusion; non-null values written;NON_EMPTYcontent inclusion still suppresses""unchanged; - flag on only affects
JsonNullableproperties (plainString nullstill written underALWAYS); - root values / collection elements /
Mapvalues unchanged under the flag; - deserialization unaffected; write-with-flag round-trip.
Full mvn test passes (423 tests, 0 failures).
Summary by cubic
Fixes #51 by adding an opt-in allowSuppressNulls(boolean) flag to JsonNullableModule and JsonNullableJackson3Module that omits null-valued JsonNullable bean properties from serialized output.
With the flag off (the default) output is byte-for-byte identical to today: JsonNullable.of(null) still serializes as an explicit JSON null, even under NON_NULL / NON_ABSENT inclusion. With the flag on, JsonNullable bean properties holding of(null), ofNull(), undefined(), or a plain null reference are omitted entirely.
Notes
- Suppression happens in the bean property writers (plain and unwrapping, Jackson 2 and 3), so root values, array elements, collection elements, and
Mapvalues still serialize asnull. - Non-
JsonNullableproperties and deserialization are unaffected. - The protected
(BeanPropertyWriter, PropertyName)constructors are preserved and delegate with the flag off, so existing subclasses stay source- and binary-compatible;JsonNullableBeanPropertyWriterSubclassTestguards this. - README default-behavior claims are scoped to bean properties.