From 1179e5180fe2de783baf6e5d72363c2b8874b63d Mon Sep 17 00:00:00 2001 From: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:15:41 +0900 Subject: [PATCH 1/2] Fix #6101: omit NON_EMPTY Map when CUSTOM content filter removes all entries MapSerializer.isEmpty() compared the content filter to the whole Map instead of to each entry value, so a Map emptied by a CUSTOM contentFilter was considered non-empty and written as {}. Compare per entry value, matching the write path and the content=NON_EMPTY branch. --- .../databind/ser/jdk/MapSerializer.java | 4 +- .../JsonIncludeMapContentFilter6101Test.java | 95 +++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/test/java/tools/jackson/databind/ser/filter/JsonIncludeMapContentFilter6101Test.java diff --git a/src/main/java/tools/jackson/databind/ser/jdk/MapSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/MapSerializer.java index 776217d597..2b005c764a 100644 --- a/src/main/java/tools/jackson/databind/ser/jdk/MapSerializer.java +++ b/src/main/java/tools/jackson/databind/ser/jdk/MapSerializer.java @@ -495,7 +495,7 @@ public boolean isEmpty(SerializationContext prov, Map value) if (!valueSer.isEmpty(prov, elemValue)) { return false; } - } else if ((supp == null) || !supp.equals(value)) { + } else if ((supp == null) || !supp.equals(elemValue)) { return false; } } @@ -514,7 +514,7 @@ public boolean isEmpty(SerializationContext prov, Map value) if (!valueSer.isEmpty(prov, elemValue)) { return false; } - } else if ((supp == null) || !supp.equals(value)) { + } else if ((supp == null) || !supp.equals(elemValue)) { return false; } } diff --git a/src/test/java/tools/jackson/databind/ser/filter/JsonIncludeMapContentFilter6101Test.java b/src/test/java/tools/jackson/databind/ser/filter/JsonIncludeMapContentFilter6101Test.java new file mode 100644 index 0000000000..5a683acaa0 --- /dev/null +++ b/src/test/java/tools/jackson/databind/ser/filter/JsonIncludeMapContentFilter6101Test.java @@ -0,0 +1,95 @@ +package tools.jackson.databind.ser.filter; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.testutil.DatabindTestUtil; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// [databind#6101]: a NON_EMPTY Map property whose entries are all removed by a +// CUSTOM content filter should be treated as empty and omitted, matching the +// behavior for content=NON_EMPTY and the (already correct) write path. +public class JsonIncludeMapContentFilter6101Test extends DatabindTestUtil +{ + // equals() returns true for values the content filter should suppress + static class FooFilter { + @Override + public boolean equals(Object other) { + return "foo".equals(other); + } + + @Override + public int hashCode() { return 0; } + } + + // Statically-typed value serializer (String) -> isEmpty() static-serializer loop + static class Bean { + @JsonInclude(value = JsonInclude.Include.NON_EMPTY, + content = JsonInclude.Include.CUSTOM, + contentFilter = FooFilter.class) + public Map stuff = new LinkedHashMap<>(); + + public Bean add(String key, String value) { + stuff.put(key, value); + return this; + } + } + + // Dynamically-resolved value serializer (Object) -> isEmpty() fallback loop + static class DynBean { + @JsonInclude(value = JsonInclude.Include.NON_EMPTY, + content = JsonInclude.Include.CUSTOM, + contentFilter = FooFilter.class) + public Map stuff = new LinkedHashMap<>(); + + public DynBean add(String key, Object value) { + stuff.put(key, value); + return this; + } + } + + private final ObjectMapper MAPPER = new ObjectMapper(); + + // Before the fix this produced {"stuff":{}} because MapSerializer.isEmpty() + // compared the content filter to the whole Map instead of to each entry value. + @Test + public void mapEmptyAfterContentFilter() throws Exception { + Bean bean = new Bean().add("a", "foo").add("b", "foo"); + assertEquals("{}", MAPPER.writeValueAsString(bean)); + } + + @Test + public void mapKeepsSurvivingEntries() throws Exception { + Bean bean = new Bean().add("a", "foo").add("b", "keep"); + assertEquals(""" + {"stuff":{"b":"keep"}}""", + MAPPER.writeValueAsString(bean)); + } + + @Test + public void genuinelyEmptyMapOmitted() throws Exception { + assertEquals("{}", MAPPER.writeValueAsString(new Bean())); + } + + // Same bug on the fallback loop taken when the value serializer is not + // statically known (Map). + @Test + public void dynamicValueMapEmptyAfterContentFilter() throws Exception { + DynBean bean = new DynBean().add("a", "foo").add("b", "foo"); + assertEquals("{}", MAPPER.writeValueAsString(bean)); + } + + @Test + public void dynamicValueMapKeepsSurvivingEntries() throws Exception { + DynBean bean = new DynBean().add("a", "foo").add("b", "keep"); + assertEquals(""" + {"stuff":{"b":"keep"}}""", + MAPPER.writeValueAsString(bean)); + } +} From 6929df8463d051a208e354374e5fb1ab9aba8670 Mon Sep 17 00:00:00 2001 From: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:09:01 +0900 Subject: [PATCH 2/2] Add release-notes VERSION + CREDITS entries for #6101 Per maintainer request: add the 3.2.2 VERSION entry and CREDITS entries (reporter @dlwldnjs1009, fix @seonwooj0810) for the Map CUSTOM content-filter emptiness fix. --- release-notes/CREDITS | 6 ++++++ release-notes/VERSION | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/release-notes/CREDITS b/release-notes/CREDITS index d82e788ff3..29ee9abc73 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -250,6 +250,9 @@ Lee Jiwon (@dlwldnjs1009) * Contributed #6022: Follow-up to #5369: apply content @JsonInclude in `StringCollectionSerializer.serializeWithType` [3.2.1] + * Reported #6101: `@JsonInclude(NON_EMPTY, content=CUSTOM)` does not omit a Map property + after all entries are filtered + [3.2.2] Garret Wilson (@garretwilson) * Suggested #4157: Add `MapperFeature.INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY` to ignore @@ -596,6 +599,9 @@ Théo Szanto (@indyteo) * Fixed #6065: `SerializationFeature.APPLY_JSON_INCLUDE_FOR_CONTAINERS` does not fully remove empty collection during serialization [3.2.1] + * Fixed #6101: `@JsonInclude(NON_EMPTY, content=CUSTOM)` does not omit a Map property + after all entries are filtered + [3.2.2] @doeiqts * Reported #6065: `SerializationFeature.APPLY_JSON_INCLUDE_FOR_CONTAINERS` does not fully diff --git a/release-notes/VERSION b/release-notes/VERSION index cf03e193d0..d8a3da48d5 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -9,6 +9,13 @@ Versions: 3.x (for earlier see VERSION-2.x) No changes since 3.2 +3.2.2 (not yet released) + +#6101: `@JsonInclude(NON_EMPTY, content=CUSTOM)` does not omit a Map property + after all entries are filtered + (reported by @dlwldnjs1009) + (fix by @seonwooj0810) + 3.2.1 (10-Jul-2026) #6022: Follow-up to #5369: apply content @JsonInclude in