From fbc18c308c2d29a4c949c09a5a09df8e99ffedd2 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 3 Aug 2025 22:05:45 +0900 Subject: [PATCH 01/10] Support JsonInclude for List Serializer --- .../ser/impl/IndexedListSerializer.java | 2 + .../ser/impl/IndexedStringListSerializer.java | 20 ++ .../ser/impl/StringCollectionSerializer.java | 11 + .../ser/std/CollectionSerializer.java | 2 + .../ser/std/StaticListSerializerBase.java | 92 ++++++- .../ser/filter/JsonIncludeCustomTest.java | 24 ++ .../JsonIncludeForCollection5227Test.java | 254 ++++++++++++++++++ 7 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeForCollection5227Test.java diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java index dc4b320569..7a544634a7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java @@ -88,6 +88,8 @@ public void serializeContents(List value, JsonGenerator g, SerializerProvider serializeContentsUsing(value, g, provider, _elementSerializer); return; } + // TODO: Add support for suppressableValue filtering like MapSerializer.serializeOptionalFields() + // Need to check each element against suppressableValue filter and skip matching elements if (_valueTypeSerializer != null) { serializeTypedContents(value, g, provider); return; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java index 48f5d91fec..15fb826e9a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java @@ -41,11 +41,22 @@ public IndexedStringListSerializer(IndexedStringListSerializer src, Boolean unwrapSingle) { super(src, unwrapSingle); } + + public IndexedStringListSerializer(IndexedStringListSerializer src, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + super(src, unwrapSingle, suppressableValue, suppressNulls); + } @Override public JsonSerializer _withResolved(BeanProperty prop, Boolean unwrapSingle) { return new IndexedStringListSerializer(this, unwrapSingle); } + + @Override + public JsonSerializer _withResolved(BeanProperty prop, Boolean unwrapSingle, + Object suppressableValue, boolean suppressNulls) { + return new IndexedStringListSerializer(this, unwrapSingle, suppressableValue, suppressNulls); + } @Override protected JsonNode contentSchema() { return createSchemaNode("string", true); } @@ -98,8 +109,17 @@ private final void serializeContents(List value, JsonGenerator g, for (; i < len; ++i) { String str = value.get(i); if (str == null) { + if (_suppressNulls) { + continue; + } provider.defaultSerializeNull(g); } else { + // Check if this element should be suppressed + if (_suppressableValue != null) { + if (_suppressableValue.equals(str)) { + continue; // Skip this element + } + } g.writeString(str); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java index be2e52eab7..909bea2fb4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java @@ -42,11 +42,22 @@ protected StringCollectionSerializer(StringCollectionSerializer src, super(src, unwrapSingle); } + protected StringCollectionSerializer(StringCollectionSerializer src, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) + { + super(src, unwrapSingle, suppressableValue, suppressNulls); + } + @Override public JsonSerializer _withResolved(BeanProperty prop, Boolean unwrapSingle) { return new StringCollectionSerializer(this, unwrapSingle); } + @Override + public JsonSerializer _withResolved(BeanProperty prop, Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + return new StringCollectionSerializer(this, unwrapSingle, suppressableValue, suppressNulls); + } + @Override protected JsonNode contentSchema() { return createSchemaNode("string", true); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index 75202d2734..c264b562ec 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -120,6 +120,8 @@ public void serializeContents(Collection value, JsonGenerator g, SerializerPr serializeContentsUsing(value, g, provider, _elementSerializer); return; } + // TODO: Add support for suppressableValue filtering like MapSerializer.serializeOptionalFields() + // Need to check each element against suppressableValue filter and skip matching elements Iterator it = value.iterator(); if (!it.hasNext()) { return; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java index a641266db8..8e22301a5d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java @@ -6,6 +6,7 @@ import java.util.Objects; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.*; @@ -14,6 +15,8 @@ import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import com.fasterxml.jackson.databind.util.ArrayBuilders; +import com.fasterxml.jackson.databind.util.BeanUtil; /** * Intermediate base class for Lists, Collections and Arrays @@ -24,6 +27,7 @@ public abstract class StaticListSerializerBase> extends StdSerializer implements ContextualSerializer { + public final static Object MARKER_FOR_EMPTY = JsonInclude.Include.NON_EMPTY; /** * Setting for specific local override for "unwrap single element arrays": * true for enable unwrapping, false for preventing it, `null` for using @@ -32,10 +36,26 @@ public abstract class StaticListSerializerBase> * @since 2.6 */ protected final Boolean _unwrapSingle; + + /** + * Value that indicates suppression mechanism to use for + * content values (elements of container), if any; null + * for no filtering. + * @since 2.20 + */ + protected final Object _suppressableValue; + + /** + * Flag that indicates whether nulls should be suppressed. + * @since 2.20 + */ + protected final boolean _suppressNulls; protected StaticListSerializerBase(Class cls) { super(cls, false); _unwrapSingle = null; + _suppressableValue = null; + _suppressNulls = false; } /** @@ -45,6 +65,19 @@ protected StaticListSerializerBase(StaticListSerializerBase src, Boolean unwrapSingle) { super(src); _unwrapSingle = unwrapSingle; + _suppressableValue = src._suppressableValue; + _suppressNulls = src._suppressNulls; + } + + /** + * @since 2.20 + */ + protected StaticListSerializerBase(StaticListSerializerBase src, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + super(src); + _unwrapSingle = unwrapSingle; + _suppressableValue = suppressableValue; + _suppressNulls = suppressNulls; } /** @@ -52,6 +85,12 @@ protected StaticListSerializerBase(StaticListSerializerBase src, */ public abstract JsonSerializer _withResolved(BeanProperty prop, Boolean unwrapSingle); + + /** + * @since 2.20 + */ + public abstract JsonSerializer _withResolved(BeanProperty prop, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls); /* /********************************************************** @@ -87,12 +126,61 @@ public JsonSerializer createContextual(SerializerProvider serializers, if (ser == null) { ser = serializers.findContentValueSerializer(String.class, property); } + + // Handle content inclusion (similar to MapSerializer lines 560-609) + JsonInclude.Value inclV = findIncludeOverrides(serializers, property, List.class); + Object valueToSuppress = _suppressableValue; + boolean suppressNulls = _suppressNulls; + + if (inclV != null) { + JsonInclude.Include incl = inclV.getContentInclusion(); + if (incl != JsonInclude.Include.USE_DEFAULTS) { + switch (incl) { + case NON_DEFAULT: + valueToSuppress = BeanUtil.getDefaultValue(serializers.constructType(String.class)); + suppressNulls = true; + if (valueToSuppress != null) { + if (valueToSuppress.getClass().isArray()) { + valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress); + } + } + break; + case NON_ABSENT: + suppressNulls = true; + valueToSuppress = MARKER_FOR_EMPTY; + break; + case NON_EMPTY: + suppressNulls = true; + valueToSuppress = MARKER_FOR_EMPTY; + break; + case CUSTOM: + valueToSuppress = serializers.includeFilterInstance(null, inclV.getContentFilter()); + if (valueToSuppress == null) { + suppressNulls = true; + } else { + suppressNulls = serializers.includeFilterSuppressNulls(valueToSuppress); + } + break; + case NON_NULL: + valueToSuppress = null; + suppressNulls = true; + break; + case ALWAYS: + default: + valueToSuppress = null; + suppressNulls = false; + break; + } + } + } // Optimization: default serializer just writes String, so we can avoid a call: if (isDefaultSerializer(ser)) { - if (Objects.equals(unwrapSingle, _unwrapSingle)) { + if (Objects.equals(unwrapSingle, _unwrapSingle) + && Objects.equals(valueToSuppress, _suppressableValue) + && suppressNulls == _suppressNulls) { return this; } - return _withResolved(property, unwrapSingle); + return _withResolved(property, unwrapSingle, valueToSuppress, suppressNulls); } // otherwise... // note: will never have TypeSerializer, because Strings are "natural" type diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java index 3a234d4d56..0e37c66ba8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java @@ -1,6 +1,8 @@ package com.fasterxml.jackson.databind.ser.filter; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -56,6 +58,17 @@ public FooMapBean add(String key, String value) { } } + static class FooListBean { + @JsonInclude(content=JsonInclude.Include.CUSTOM, + contentFilter=FooFilter.class) + public List items = new ArrayList(); + + public FooListBean add(String value) { + items.add(value); + return this; + } + } + static class BrokenBean { @JsonInclude(value=JsonInclude.Include.CUSTOM, valueFilter=BrokenFilter.class) @@ -109,6 +122,17 @@ public void testCustomFilterWithMap() throws Exception assertEquals(a2q("{'stuff':{'a':'1','c':'2'}}"), MAPPER.writeValueAsString(input)); } + @Test + public void testCustomFilterWithList() throws Exception + { + FooListBean input = new FooListBean() + .add("1") + .add("foo") + .add("2"); + + assertEquals(a2q("{'items':['1','2']}"), MAPPER.writeValueAsString(input)); + } + // [databind#3481] @Test public void testRepeatedCalls() throws Exception diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeForCollection5227Test.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeForCollection5227Test.java new file mode 100644 index 0000000000..b8d4256215 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeForCollection5227Test.java @@ -0,0 +1,254 @@ +package com.fasterxml.jackson.databind.ser.filter; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +// Tests for [databind#888] +public class JsonIncludeForCollection5227Test extends DatabindTestUtil +{ + static class FooFilter { + @Override + public boolean equals(Object other) { + if (other == null) { // do NOT filter out nulls + return false; + } + // in fact, only filter out exact String "foo" + return "foo".equals(other); + } + } + + static class FooListBean { + @JsonInclude(content=JsonInclude.Include.CUSTOM, + contentFilter=FooFilter.class) + public List items = new ArrayList(); + + public FooListBean add(String value) { + items.add(value); + return this; + } + } + + // Test NON_NULL content inclusion + static class NonNullListBean { + @JsonInclude(content=JsonInclude.Include.NON_NULL) + public List items = new ArrayList(); + + public NonNullListBean add(String value) { + items.add(value); + return this; + } + } + + // Test NON_EMPTY content inclusion + static class NonEmptyListBean { + @JsonInclude(content=JsonInclude.Include.NON_EMPTY) + public List items = new ArrayList(); + + public NonEmptyListBean add(String value) { + items.add(value); + return this; + } + } + + // Test NON_DEFAULT content inclusion + static class NonDefaultListBean { + @JsonInclude(content=JsonInclude.Include.NON_DEFAULT) + public List items = new ArrayList(); + + public NonDefaultListBean add(String value) { + items.add(value); + return this; + } + } + + // Test with different collection types + static class FooSetBean { + @JsonInclude(content=JsonInclude.Include.CUSTOM, + contentFilter=FooFilter.class) + public Set items = new LinkedHashSet(); + + public FooSetBean add(String value) { + items.add(value); + return this; + } + } + + // Test with Integer values + static class NumberFilter { + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + return Integer.valueOf(42).equals(other); + } + } + + static class NumberListBean { + @JsonInclude(content=JsonInclude.Include.CUSTOM, + contentFilter=NumberFilter.class) + public List numbers = new ArrayList(); + + public NumberListBean add(Integer value) { + numbers.add(value); + return this; + } + } + + // Test counting filter behavior + static class CountingFooFilter { + public final static AtomicInteger counter = new AtomicInteger(0); + + @Override + public boolean equals(Object other) { + counter.incrementAndGet(); + return "foo".equals(other); + } + } + + static class CountingFooListBean { + @JsonInclude(content=JsonInclude.Include.CUSTOM, + contentFilter=CountingFooFilter.class) + public List items = new ArrayList(); + + public CountingFooListBean add(String value) { + items.add(value); + return this; + } + } + + /* + /********************************************************** + /* Test methods, success + /********************************************************** + */ + + final private ObjectMapper MAPPER = new ObjectMapper(); + + @Test + public void testCustomFilterWithList() throws Exception + { + FooListBean input = new FooListBean() + .add("1") + .add("foo") + .add("2"); + + assertEquals(a2q("{'items':['1','2']}"), MAPPER.writeValueAsString(input)); + } + + @Test + public void testNonNullContentInclusion() throws Exception + { + NonNullListBean input = new NonNullListBean() + .add("1") + .add(null) + .add("2"); + + assertEquals(a2q("{'items':['1','2']}"), MAPPER.writeValueAsString(input)); + } + + @Test + public void testNonEmptyContentInclusion() throws Exception + { + NonEmptyListBean input = new NonEmptyListBean() + .add("1") + .add("") + .add("2"); + + assertEquals(a2q("{'items':['1','2']}"), MAPPER.writeValueAsString(input)); + } + + @Test + public void testNonDefaultContentInclusion() throws Exception + { + NonDefaultListBean input = new NonDefaultListBean() + .add("1") + .add(null) // null is default for String + .add("2"); + + assertEquals(a2q("{'items':['1','2']}"), MAPPER.writeValueAsString(input)); + } + + @Test + public void testCustomFilterWithSet() throws Exception + { + FooSetBean input = new FooSetBean() + .add("1") + .add("foo") + .add("2"); + + assertEquals(a2q("{'items':['1','2']}"), MAPPER.writeValueAsString(input)); + } + + @Test + public void testCustomFilterWithNumbers() throws Exception + { + NumberListBean input = new NumberListBean() + .add(1) + .add(42) + .add(3); + + assertEquals(a2q("{'numbers':[1,3]}"), MAPPER.writeValueAsString(input)); + } + + @Test + public void testCountingFilterCalls() throws Exception + { + CountingFooFilter.counter.set(0); + + CountingFooListBean input = new CountingFooListBean() + .add("1") + .add("foo") + .add("2") + .add("foo") + .add("3"); + + assertEquals(a2q("{'items':['1','2','3']}"), MAPPER.writeValueAsString(input)); + // Should be called once for each element + assertEquals(5, CountingFooFilter.counter.get()); + } + + @Test + public void testEmptyListWithCustomFilter() throws Exception + { + FooListBean input = new FooListBean(); + assertEquals(a2q("{'items':[]}"), MAPPER.writeValueAsString(input)); + } + + @Test + public void testAllFilteredOut() throws Exception + { + FooListBean input = new FooListBean() + .add("foo") + .add("foo") + .add("foo"); + + assertEquals(a2q("{'items':[]}"), MAPPER.writeValueAsString(input)); + } + + @Test + public void testMixedNullsAndFiltered() throws Exception + { + FooListBean input = new FooListBean() + .add("1") + .add(null) + .add("foo") + .add("2") + .add(null); + + // Custom filter should not filter nulls (based on FooFilter.equals implementation) + assertEquals(a2q("{'items':['1',null,'2',null]}"), MAPPER.writeValueAsString(input)); + } + + +} From 318b80cb2fda5eb5b8d6f9bca587ea6c23cdd77d Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 3 Aug 2025 22:57:42 +0900 Subject: [PATCH 02/10] 5227 Collection --- .../ser/impl/IndexedListSerializer.java | 176 +++++++++++++++++- .../databind/ser/impl/IteratorSerializer.java | 109 ++++++++++- .../ser/std/AsArraySerializerBase.java | 103 +++++++++- .../ser/std/CollectionSerializer.java | 138 +++++++++++++- .../databind/ser/std/EnumSetSerializer.java | 13 ++ .../databind/ser/std/IterableSerializer.java | 13 ++ 6 files changed, 540 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java index 7a544634a7..81c2de4755 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java @@ -32,6 +32,12 @@ public IndexedListSerializer(IndexedListSerializer src, Boolean unwrapSingle) { super(src, property, vts, valueSerializer, unwrapSingle); } + + public IndexedListSerializer(IndexedListSerializer src, + BeanProperty property, TypeSerializer vts, JsonSerializer valueSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + super(src, property, vts, valueSerializer, unwrapSingle, suppressableValue, suppressNulls); + } @Override public IndexedListSerializer withResolved(BeanProperty property, @@ -39,6 +45,13 @@ public IndexedListSerializer withResolved(BeanProperty property, Boolean unwrapSingle) { return new IndexedListSerializer(this, property, vts, elementSerializer, unwrapSingle); } + + @Override + public IndexedListSerializer withResolved(BeanProperty property, + TypeSerializer vts, JsonSerializer elementSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + return new IndexedListSerializer(this, property, vts, elementSerializer, unwrapSingle, suppressableValue, suppressNulls); + } /* /********************************************************** @@ -71,12 +84,20 @@ public final void serialize(List value, JsonGenerator gen, SerializerProvider if (((_unwrapSingle == null) && provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)) || (_unwrapSingle == Boolean.TRUE)) { - serializeContents(value, gen, provider); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, gen, provider); + } else { + serializeContents(value, gen, provider); + } return; } } gen.writeStartArray(value, len); - serializeContents(value, gen, provider); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, gen, provider); + } else { + serializeContents(value, gen, provider); + } gen.writeEndArray(); } @@ -88,8 +109,6 @@ public void serializeContents(List value, JsonGenerator g, SerializerProvider serializeContentsUsing(value, g, provider, _elementSerializer); return; } - // TODO: Add support for suppressableValue filtering like MapSerializer.serializeOptionalFields() - // Need to check each element against suppressableValue filter and skip matching elements if (_valueTypeSerializer != null) { serializeTypedContents(value, g, provider); return; @@ -126,6 +145,63 @@ public void serializeContents(List value, JsonGenerator g, SerializerProvider } } + public void serializeFilteredContents(List value, JsonGenerator g, SerializerProvider provider) + throws IOException + { + if (_elementSerializer != null) { + serializeFilteredContentsUsing(value, g, provider, _elementSerializer); + return; + } + if (_valueTypeSerializer != null) { + serializeFilteredTypedContents(value, g, provider); + return; + } + final int len = value.size(); + if (len == 0) { + return; + } + int i = 0; + try { + PropertySerializerMap serializers = _dynamicSerializers; + for (; i < len; ++i) { + Object elem = value.get(i); + if (elem == null) { + if (_suppressNulls) { + continue; + } + provider.defaultSerializeNull(g); + } else { + Class cc = elem.getClass(); + JsonSerializer serializer = serializers.serializerFor(cc); + if (serializer == null) { + // To fix [JACKSON-508] + if (_elementType.hasGenericTypes()) { + serializer = _findAndAddDynamic(serializers, + provider.constructSpecializedType(_elementType, cc), provider); + } else { + serializer = _findAndAddDynamic(serializers, cc, provider); + } + serializers = _dynamicSerializers; + } + // Check if this element should be suppressed + if (_suppressableValue != null) { + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty values using serializer + if (serializer.isEmpty(provider, elem)) { + continue; // Skip empty elements + } + } else if (_suppressableValue.equals(elem)) { + continue; // Skip this element + } + } + serializer.serialize(elem, g, provider); + } + } + } catch (Exception e) { + wrapAndThrow(provider, e, value, i); + } + } + public void serializeContentsUsing(List value, JsonGenerator jgen, SerializerProvider provider, JsonSerializer ser) throws IOException @@ -152,6 +228,48 @@ public void serializeContentsUsing(List value, JsonGenerator jgen, Serializer } } + public void serializeFilteredContentsUsing(List value, JsonGenerator jgen, SerializerProvider provider, + JsonSerializer ser) + throws IOException + { + final int len = value.size(); + if (len == 0) { + return; + } + final TypeSerializer typeSer = _valueTypeSerializer; + for (int i = 0; i < len; ++i) { + Object elem = value.get(i); + try { + if (elem == null) { + if (_suppressNulls) { + continue; + } + provider.defaultSerializeNull(jgen); + } else { + // Check if this element should be suppressed + if (_suppressableValue != null) { + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty values using serializer + if (ser.isEmpty(provider, elem)) { + continue; // Skip empty elements + } + } else if (_suppressableValue.equals(elem)) { + continue; // Skip this element + } + } + if (typeSer == null) { + ser.serialize(elem, jgen, provider); + } else { + ser.serializeWithType(elem, jgen, provider, typeSer); + } + } + } catch (Exception e) { + // [JACKSON-55] Need to add reference information + wrapAndThrow(provider, e, value, i); + } + } + } + public void serializeTypedContents(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException { @@ -187,4 +305,54 @@ public void serializeTypedContents(List value, JsonGenerator jgen, Serializer wrapAndThrow(provider, e, value, i); } } + + public void serializeFilteredTypedContents(List value, JsonGenerator jgen, SerializerProvider provider) + throws IOException + { + final int len = value.size(); + if (len == 0) { + return; + } + int i = 0; + try { + final TypeSerializer typeSer = _valueTypeSerializer; + PropertySerializerMap serializers = _dynamicSerializers; + for (; i < len; ++i) { + Object elem = value.get(i); + if (elem == null) { + if (_suppressNulls) { + continue; + } + provider.defaultSerializeNull(jgen); + } else { + Class cc = elem.getClass(); + JsonSerializer serializer = serializers.serializerFor(cc); + if (serializer == null) { + // To fix [JACKSON-508] + if (_elementType.hasGenericTypes()) { + serializer = _findAndAddDynamic(serializers, + provider.constructSpecializedType(_elementType, cc), provider); + } else { + serializer = _findAndAddDynamic(serializers, cc, provider); + } + serializers = _dynamicSerializers; + } + // Check if this element should be suppressed + if (_suppressableValue != null) { + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty values using serializer + if (serializer.isEmpty(provider, elem)) { + continue; // Skip empty elements + } + } else if (_suppressableValue.equals(elem)) { + continue; // Skip this element + } + } + serializer.serializeWithType(elem, jgen, provider, typeSer); + } + } + } catch (Exception e) { + wrapAndThrow(provider, e, value, i); + } + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java index 10aba9b654..7801bc9e08 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java @@ -25,6 +25,12 @@ public IteratorSerializer(IteratorSerializer src, super(src, property, vts, valueSerializer, unwrapSingle); } + public IteratorSerializer(IteratorSerializer src, + BeanProperty property, TypeSerializer vts, JsonSerializer valueSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + super(src, property, vts, valueSerializer, unwrapSingle, suppressableValue, suppressNulls); + } + @Override public boolean isEmpty(SerializerProvider prov, Iterator value) { return !value.hasNext(); @@ -48,6 +54,13 @@ public IteratorSerializer withResolved(BeanProperty property, return new IteratorSerializer(this, property, vts, elementSerializer, unwrapSingle); } + @Override + public IteratorSerializer withResolved(BeanProperty property, + TypeSerializer vts, JsonSerializer elementSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + return new IteratorSerializer(this, property, vts, elementSerializer, unwrapSingle, suppressableValue, suppressNulls); + } + @Override public final void serialize(Iterator value, JsonGenerator gen, SerializerProvider provider) throws IOException @@ -58,13 +71,21 @@ public final void serialize(Iterator value, JsonGenerator gen, provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)) || (_unwrapSingle == Boolean.TRUE)) { if (hasSingleElement(value)) { - serializeContents(value, gen, provider); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, gen, provider); + } else { + serializeContents(value, gen, provider); + } return; } } */ gen.writeStartArray(value); - serializeContents(value, gen, provider); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, gen, provider); + } else { + serializeContents(value, gen, provider); + } gen.writeEndArray(); } @@ -93,6 +114,46 @@ public void serializeContents(Iterator value, JsonGenerator g, } while (value.hasNext()); } + public void serializeFilteredContents(Iterator value, JsonGenerator g, + SerializerProvider provider) throws IOException + { + if (!value.hasNext()) { + return; + } + JsonSerializer serializer = _elementSerializer; + if (serializer == null) { + _serializeFilteredDynamicContents(value, g, provider); + return; + } + final TypeSerializer typeSer = _valueTypeSerializer; + do { + Object elem = value.next(); + if (elem == null) { + if (_suppressNulls) { + continue; + } + provider.defaultSerializeNull(g); + } else { + // Check if this element should be suppressed + if (_suppressableValue != null) { + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty values using serializer + if (serializer.isEmpty(provider, elem)) { + continue; // Skip empty elements + } + } else if (_suppressableValue.equals(elem)) { + continue; // Skip this element + } + } + if (typeSer == null) { + serializer.serialize(elem, g, provider); + } else { + serializer.serializeWithType(elem, g, provider, typeSer); + } + } + } while (value.hasNext()); + } + protected void _serializeDynamicContents(Iterator value, JsonGenerator g, SerializerProvider provider) throws IOException { @@ -122,4 +183,48 @@ protected void _serializeDynamicContents(Iterator value, JsonGenerator g, } } while (value.hasNext()); } + + protected void _serializeFilteredDynamicContents(Iterator value, JsonGenerator g, + SerializerProvider provider) throws IOException + { + final TypeSerializer typeSer = _valueTypeSerializer; + PropertySerializerMap serializers = _dynamicSerializers; + do { + Object elem = value.next(); + if (elem == null) { + if (_suppressNulls) { + continue; + } + provider.defaultSerializeNull(g); + continue; + } + Class cc = elem.getClass(); + JsonSerializer serializer = serializers.serializerFor(cc); + if (serializer == null) { + if (_elementType.hasGenericTypes()) { + serializer = _findAndAddDynamic(serializers, + provider.constructSpecializedType(_elementType, cc), provider); + } else { + serializer = _findAndAddDynamic(serializers, cc, provider); + } + serializers = _dynamicSerializers; + } + // Check if this element should be suppressed + if (_suppressableValue != null) { + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty values using serializer + if (serializer.isEmpty(provider, elem)) { + continue; // Skip empty elements + } + } else if (_suppressableValue.equals(elem)) { + continue; // Skip this element + } + } + if (typeSer == null) { + serializer.serialize(elem, g, provider); + } else { + serializer.serializeWithType(elem, g, provider, typeSer); + } + } while (value.hasNext()); + } } \ No newline at end of file diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java index a241080700..87179257d2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java @@ -5,6 +5,7 @@ import java.util.Objects; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.type.WritableTypeId; @@ -16,6 +17,8 @@ import com.fasterxml.jackson.databind.ser.ContainerSerializer; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap; +import com.fasterxml.jackson.databind.util.ArrayBuilders; +import com.fasterxml.jackson.databind.util.BeanUtil; /** * Base class for serializers that will output contents as JSON @@ -27,6 +30,7 @@ public abstract class AsArraySerializerBase extends ContainerSerializer implements ContextualSerializer { + public final static Object MARKER_FOR_EMPTY = JsonInclude.Include.NON_EMPTY; protected final JavaType _elementType; /** @@ -61,6 +65,20 @@ public abstract class AsArraySerializerBase */ protected PropertySerializerMap _dynamicSerializers; + /** + * Value that indicates suppression mechanism to use for + * content values (elements of container), if any; null + * for no filtering. + * @since 2.20 + */ + protected final Object _suppressableValue; + + /** + * Flag that indicates whether nulls should be suppressed. + * @since 2.20 + */ + protected final boolean _suppressNulls; + /* /********************************************************** /* Life-cycle @@ -110,6 +128,8 @@ protected AsArraySerializerBase(Class cls, JavaType elementType, boolean stat _elementSerializer = (JsonSerializer) elementSerializer; _dynamicSerializers = PropertySerializerMap.emptyForProperties(); _unwrapSingle = unwrapSingle; + _suppressableValue = null; + _suppressNulls = false; } @SuppressWarnings("unchecked") @@ -126,6 +146,28 @@ protected AsArraySerializerBase(AsArraySerializerBase src, // [databind#2181]: may not be safe to reuse, start from empty _dynamicSerializers = PropertySerializerMap.emptyForProperties(); _unwrapSingle = unwrapSingle; + _suppressableValue = src._suppressableValue; + _suppressNulls = src._suppressNulls; + } + + /** + * @since 2.20 + */ + @SuppressWarnings("unchecked") + protected AsArraySerializerBase(AsArraySerializerBase src, + BeanProperty property, TypeSerializer vts, JsonSerializer elementSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) + { + super(src); + _elementType = src._elementType; + _staticTyping = src._staticTyping; + _valueTypeSerializer = vts; + _property = property; + _elementSerializer = (JsonSerializer) elementSerializer; + _dynamicSerializers = PropertySerializerMap.emptyForProperties(); + _unwrapSingle = unwrapSingle; + _suppressableValue = suppressableValue; + _suppressNulls = suppressNulls; } /** @@ -153,6 +195,13 @@ public final AsArraySerializerBase withResolved(BeanProperty property, public abstract AsArraySerializerBase withResolved(BeanProperty property, TypeSerializer vts, JsonSerializer elementSerializer, Boolean unwrapSingle); + + /** + * @since 2.20 + */ + public abstract AsArraySerializerBase withResolved(BeanProperty property, + TypeSerializer vts, JsonSerializer elementSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls); /* /********************************************************** @@ -207,11 +256,61 @@ public JsonSerializer createContextual(SerializerProvider serializers, } } } + + // Handle content inclusion (similar to MapSerializer lines 560-609) + JsonInclude.Value inclV = findIncludeOverrides(serializers, property, handledType()); + Object valueToSuppress = _suppressableValue; + boolean suppressNulls = _suppressNulls; + + if (inclV != null) { + JsonInclude.Include incl = inclV.getContentInclusion(); + if (incl != JsonInclude.Include.USE_DEFAULTS) { + switch (incl) { + case NON_DEFAULT: + valueToSuppress = BeanUtil.getDefaultValue(_elementType); + suppressNulls = true; + if (valueToSuppress != null) { + if (valueToSuppress.getClass().isArray()) { + valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress); + } + } + break; + case NON_ABSENT: + suppressNulls = true; + valueToSuppress = MARKER_FOR_EMPTY; + break; + case NON_EMPTY: + suppressNulls = true; + valueToSuppress = MARKER_FOR_EMPTY; + break; + case CUSTOM: + valueToSuppress = serializers.includeFilterInstance(null, inclV.getContentFilter()); + if (valueToSuppress == null) { + suppressNulls = true; + } else { + suppressNulls = serializers.includeFilterSuppressNulls(valueToSuppress); + } + break; + case NON_NULL: + valueToSuppress = null; + suppressNulls = true; + break; + case ALWAYS: + default: + valueToSuppress = null; + suppressNulls = false; + break; + } + } + } + if ((ser != _elementSerializer) || (property != _property) || (_valueTypeSerializer != typeSer) - || (!Objects.equals(_unwrapSingle, unwrapSingle))) { - return withResolved(property, typeSer, ser, unwrapSingle); + || (!Objects.equals(_unwrapSingle, unwrapSingle)) + || (!Objects.equals(valueToSuppress, _suppressableValue)) + || (suppressNulls != _suppressNulls)) { + return withResolved(property, typeSer, ser, unwrapSingle, valueToSuppress, suppressNulls); } return this; } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index c264b562ec..38cf4d51f1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -61,6 +61,13 @@ public CollectionSerializer(CollectionSerializer src, _maybeEnumSet = src._maybeEnumSet; } + public CollectionSerializer(CollectionSerializer src, + BeanProperty property, TypeSerializer vts, JsonSerializer valueSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + super(src, property, vts, valueSerializer, unwrapSingle, suppressableValue, suppressNulls); + _maybeEnumSet = src._maybeEnumSet; + } + @Override public ContainerSerializer _withValueTypeSerializer(TypeSerializer vts) { return new CollectionSerializer(this, _property, vts, _elementSerializer, _unwrapSingle); @@ -73,6 +80,13 @@ public CollectionSerializer withResolved(BeanProperty property, return new CollectionSerializer(this, property, vts, elementSerializer, unwrapSingle); } + @Override + public CollectionSerializer withResolved(BeanProperty property, + TypeSerializer vts, JsonSerializer elementSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + return new CollectionSerializer(this, property, vts, elementSerializer, unwrapSingle, suppressableValue, suppressNulls); + } + /* /********************************************************** /* Accessors @@ -103,12 +117,20 @@ public final void serialize(Collection value, JsonGenerator g, SerializerProv if (((_unwrapSingle == null) && provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)) || (_unwrapSingle == Boolean.TRUE)) { - serializeContents(value, g, provider); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, g, provider); + } else { + serializeContents(value, g, provider); + } return; } } g.writeStartArray(value, len); - serializeContents(value, g, provider); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, g, provider); + } else { + serializeContents(value, g, provider); + } g.writeEndArray(); } @@ -120,8 +142,6 @@ public void serializeContents(Collection value, JsonGenerator g, SerializerPr serializeContentsUsing(value, g, provider, _elementSerializer); return; } - // TODO: Add support for suppressableValue filtering like MapSerializer.serializeOptionalFields() - // Need to check each element against suppressableValue filter and skip matching elements Iterator it = value.iterator(); if (!it.hasNext()) { return; @@ -162,6 +182,70 @@ public void serializeContents(Collection value, JsonGenerator g, SerializerPr } } + public void serializeFilteredContents(Collection value, JsonGenerator g, SerializerProvider provider) throws IOException + { + g.assignCurrentValue(value); + if (_elementSerializer != null) { + serializeFilteredContentsUsing(value, g, provider, _elementSerializer); + return; + } + Iterator it = value.iterator(); + if (!it.hasNext()) { + return; + } + PropertySerializerMap serializers = _dynamicSerializers; + // [databind#4849]/[databind#4214]: need to check for EnumSet + final TypeSerializer typeSer = (_maybeEnumSet && value instanceof EnumSet) + ? null : _valueTypeSerializer; + + int i = 0; + try { + do { + Object elem = it.next(); + if (elem == null) { + if (_suppressNulls) { + ++i; + continue; + } + provider.defaultSerializeNull(g); + } else { + Class cc = elem.getClass(); + JsonSerializer serializer = serializers.serializerFor(cc); + if (serializer == null) { + if (_elementType.hasGenericTypes()) { + serializer = _findAndAddDynamic(serializers, + provider.constructSpecializedType(_elementType, cc), provider); + } else { + serializer = _findAndAddDynamic(serializers, cc, provider); + } + serializers = _dynamicSerializers; + } + // Check if this element should be suppressed + if (_suppressableValue != null) { + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty values using serializer + if (serializer.isEmpty(provider, elem)) { + ++i; + continue; // Skip empty elements + } + } else if (_suppressableValue.equals(elem)) { + ++i; + continue; // Skip this element + } + } + if (typeSer == null) { + serializer.serialize(elem, g, provider); + } else { + serializer.serializeWithType(elem, g, provider, typeSer); + } + } + ++i; + } while (it.hasNext()); + } catch (Exception e) { + wrapAndThrow(provider, e, value, i); + } + } + public void serializeContentsUsing(Collection value, JsonGenerator g, SerializerProvider provider, JsonSerializer ser) throws IOException { @@ -190,4 +274,50 @@ public void serializeContentsUsing(Collection value, JsonGenerator g, Seriali } while (it.hasNext()); } } + + public void serializeFilteredContentsUsing(Collection value, JsonGenerator g, SerializerProvider provider, + JsonSerializer ser) throws IOException + { + Iterator it = value.iterator(); + if (it.hasNext()) { + // [databind#4849]/[databind#4214]: need to check for EnumSet + final TypeSerializer typeSer = (_maybeEnumSet && value instanceof EnumSet) + ? null : _valueTypeSerializer; + int i = 0; + do { + Object elem = it.next(); + try { + if (elem == null) { + if (_suppressNulls) { + ++i; + continue; + } + provider.defaultSerializeNull(g); + } else { + // Check if this element should be suppressed + if (_suppressableValue != null) { + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty values using serializer + if (ser.isEmpty(provider, elem)) { + ++i; + continue; // Skip empty elements + } + } else if (_suppressableValue.equals(elem)) { + ++i; + continue; // Skip this element + } + } + if (typeSer == null) { + ser.serialize(elem, g, provider); + } else { + ser.serializeWithType(elem, g, provider, typeSer); + } + } + ++i; + } catch (Exception e) { + wrapAndThrow(provider, e, value, i); + } + } while (it.hasNext()); + } + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java index d2d14f84fd..be8c9e4bf6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java @@ -24,6 +24,12 @@ public EnumSetSerializer(EnumSetSerializer src, super(src, property, vts, valueSerializer, unwrapSingle); } + public EnumSetSerializer(EnumSetSerializer src, + BeanProperty property, TypeSerializer vts, JsonSerializer valueSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + super(src, property, vts, valueSerializer, unwrapSingle, suppressableValue, suppressNulls); + } + @Override public EnumSetSerializer _withValueTypeSerializer(TypeSerializer vts) { // no typing for enum elements (always strongly typed), so don't change @@ -37,6 +43,13 @@ public EnumSetSerializer withResolved(BeanProperty property, return new EnumSetSerializer(this, property, vts, elementSerializer, unwrapSingle); } + @Override + public EnumSetSerializer withResolved(BeanProperty property, + TypeSerializer vts, JsonSerializer elementSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + return new EnumSetSerializer(this, property, vts, elementSerializer, unwrapSingle, suppressableValue, suppressNulls); + } + @Override public boolean isEmpty(SerializerProvider prov, EnumSet> value) { return value.isEmpty(); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java index 4116ef603b..32bade29cc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java @@ -25,6 +25,12 @@ public IterableSerializer(IterableSerializer src, BeanProperty property, super(src, property, vts, valueSerializer, unwrapSingle); } + public IterableSerializer(IterableSerializer src, BeanProperty property, + TypeSerializer vts, JsonSerializer valueSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + super(src, property, vts, valueSerializer, unwrapSingle, suppressableValue, suppressNulls); + } + @Override public ContainerSerializer _withValueTypeSerializer(TypeSerializer vts) { return new IterableSerializer(this, _property, vts, _elementSerializer, _unwrapSingle); @@ -37,6 +43,13 @@ public IterableSerializer withResolved(BeanProperty property, return new IterableSerializer(this, property, vts, elementSerializer, unwrapSingle); } + @Override + public IterableSerializer withResolved(BeanProperty property, + TypeSerializer vts, JsonSerializer elementSerializer, + Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) { + return new IterableSerializer(this, property, vts, elementSerializer, unwrapSingle, suppressableValue, suppressNulls); + } + @Override public boolean isEmpty(SerializerProvider prov, Iterable value) { // Not really good way to implement this, but has to do for now: From d69502c45a2493278f6edcd8c0d93b0adfcabafd Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 3 Aug 2025 23:01:29 +0900 Subject: [PATCH 03/10] Minimize duplication --- .../ser/std/CollectionSerializer.java | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index 38cf4d51f1..cbb5f63f29 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -221,17 +221,9 @@ public void serializeFilteredContents(Collection value, JsonGenerator g, Seri serializers = _dynamicSerializers; } // Check if this element should be suppressed - if (_suppressableValue != null) { - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty values using serializer - if (serializer.isEmpty(provider, elem)) { - ++i; - continue; // Skip empty elements - } - } else if (_suppressableValue.equals(elem)) { - ++i; - continue; // Skip this element - } + if (!_shouldSerializeElement(elem, serializer, provider)) { + ++i; + continue; } if (typeSer == null) { serializer.serialize(elem, g, provider); @@ -295,17 +287,9 @@ public void serializeFilteredContentsUsing(Collection value, JsonGenerator g, provider.defaultSerializeNull(g); } else { // Check if this element should be suppressed - if (_suppressableValue != null) { - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty values using serializer - if (ser.isEmpty(provider, elem)) { - ++i; - continue; // Skip empty elements - } - } else if (_suppressableValue.equals(elem)) { - ++i; - continue; // Skip this element - } + if (!_shouldSerializeElement(elem, ser, provider)) { + ++i; + continue; } if (typeSer == null) { ser.serialize(elem, g, provider); @@ -320,4 +304,21 @@ public void serializeFilteredContentsUsing(Collection value, JsonGenerator g, } while (it.hasNext()); } } + + /** + * Helper method to determine if an element should be serialized based on content inclusion filters. + */ + private boolean _shouldSerializeElement(Object elem, JsonSerializer serializer, SerializerProvider provider) throws IOException + { + if (_suppressableValue == null) { + return true; + } + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty values using serializer + return !serializer.isEmpty(provider, elem); + } else { + // Check for custom filter match + return !_suppressableValue.equals(elem); + } + } } From 25fe5e5ce8ab9a1032b3996659c49322e684782a Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 3 Aug 2025 23:27:32 +0900 Subject: [PATCH 04/10] Support Set for JsonIncldue --- .../ser/impl/IndexedStringListSerializer.java | 37 +++++++++- .../ser/impl/StringCollectionSerializer.java | 49 ++++++++++++- .../ser/filter/JsonIncludeCustomTest.java | 24 ------- .../JsonIncludeForCollection5227Test.java | 71 ++++++------------- 4 files changed, 103 insertions(+), 78 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java index 15fb826e9a..5441f988d9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java @@ -80,12 +80,20 @@ public void serialize(List value, JsonGenerator g, if (((_unwrapSingle == null) && provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)) || (_unwrapSingle == Boolean.TRUE)) { - serializeContents(value, g, provider, 1); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, g, provider, 1); + } else { + serializeContents(value, g, provider, 1); + } return; } } g.writeStartArray(value, len); - serializeContents(value, g, provider, len); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, g, provider, len); + } else { + serializeContents(value, g, provider, len); + } g.writeEndArray(); } @@ -103,6 +111,24 @@ public void serializeWithType(List value, JsonGenerator g, SerializerPro private final void serializeContents(List value, JsonGenerator g, SerializerProvider provider, int len) throws IOException + { + int i = 0; + try { + for (; i < len; ++i) { + String str = value.get(i); + if (str == null) { + provider.defaultSerializeNull(g); + } else { + g.writeString(str); + } + } + } catch (Exception e) { + wrapAndThrow(provider, e, value, i); + } + } + + private final void serializeFilteredContents(List value, JsonGenerator g, + SerializerProvider provider, int len) throws IOException { int i = 0; try { @@ -116,7 +142,12 @@ private final void serializeContents(List value, JsonGenerator g, } else { // Check if this element should be suppressed if (_suppressableValue != null) { - if (_suppressableValue.equals(str)) { + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty strings when NON_EMPTY is used + if (str.isEmpty()) { + continue; // Skip empty strings + } + } else if (_suppressableValue.equals(str)) { continue; // Skip this element } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java index 909bea2fb4..06965c6b82 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java @@ -83,12 +83,20 @@ public void serialize(Collection value, JsonGenerator g, if (((_unwrapSingle == null) && provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)) || (_unwrapSingle == Boolean.TRUE)) { - serializeContents(value, g, provider); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, g, provider); + } else { + serializeContents(value, g, provider); + } return; } } g.writeStartArray(value, len); - serializeContents(value, g, provider); + if ((_suppressableValue != null) || _suppressNulls) { + serializeFilteredContents(value, g, provider); + } else { + serializeContents(value, g, provider); + } g.writeEndArray(); } @@ -123,4 +131,41 @@ private final void serializeContents(Collection value, JsonGenerator g, wrapAndThrow(provider, e, value, i); } } + + private final void serializeFilteredContents(Collection value, JsonGenerator g, + SerializerProvider provider) + throws IOException + { + int i = 0; + + try { + for (String str : value) { + if (str == null) { + if (_suppressNulls) { + ++i; + continue; + } + provider.defaultSerializeNull(g); + } else { + // Check if this element should be suppressed + if (_suppressableValue != null) { + if (_suppressableValue == MARKER_FOR_EMPTY) { + // Check for empty strings when NON_EMPTY is used + if (str.isEmpty()) { + ++i; + continue; // Skip empty strings + } + } else if (_suppressableValue.equals(str)) { + ++i; + continue; // Skip this element + } + } + g.writeString(str); + } + ++i; + } + } catch (Exception e) { + wrapAndThrow(provider, e, value, i); + } + } } diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java index 0e37c66ba8..3a234d4d56 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeCustomTest.java @@ -1,8 +1,6 @@ package com.fasterxml.jackson.databind.ser.filter; -import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -58,17 +56,6 @@ public FooMapBean add(String key, String value) { } } - static class FooListBean { - @JsonInclude(content=JsonInclude.Include.CUSTOM, - contentFilter=FooFilter.class) - public List items = new ArrayList(); - - public FooListBean add(String value) { - items.add(value); - return this; - } - } - static class BrokenBean { @JsonInclude(value=JsonInclude.Include.CUSTOM, valueFilter=BrokenFilter.class) @@ -122,17 +109,6 @@ public void testCustomFilterWithMap() throws Exception assertEquals(a2q("{'stuff':{'a':'1','c':'2'}}"), MAPPER.writeValueAsString(input)); } - @Test - public void testCustomFilterWithList() throws Exception - { - FooListBean input = new FooListBean() - .add("1") - .add("foo") - .add("2"); - - assertEquals(a2q("{'items':['1','2']}"), MAPPER.writeValueAsString(input)); - } - // [databind#3481] @Test public void testRepeatedCalls() throws Exception diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeForCollection5227Test.java b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeForCollection5227Test.java index b8d4256215..f317c7890c 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeForCollection5227Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/filter/JsonIncludeForCollection5227Test.java @@ -6,15 +6,14 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; // Tests for [databind#888] -public class JsonIncludeForCollection5227Test extends DatabindTestUtil +public class JsonIncludeForCollection5227Test + extends DatabindTestUtil { static class FooFilter { @Override @@ -28,8 +27,8 @@ public boolean equals(Object other) { } static class FooListBean { - @JsonInclude(content=JsonInclude.Include.CUSTOM, - contentFilter=FooFilter.class) + @JsonInclude(content = JsonInclude.Include.CUSTOM, + contentFilter = FooFilter.class) public List items = new ArrayList(); public FooListBean add(String value) { @@ -40,7 +39,7 @@ public FooListBean add(String value) { // Test NON_NULL content inclusion static class NonNullListBean { - @JsonInclude(content=JsonInclude.Include.NON_NULL) + @JsonInclude(content = JsonInclude.Include.NON_NULL) public List items = new ArrayList(); public NonNullListBean add(String value) { @@ -51,7 +50,7 @@ public NonNullListBean add(String value) { // Test NON_EMPTY content inclusion static class NonEmptyListBean { - @JsonInclude(content=JsonInclude.Include.NON_EMPTY) + @JsonInclude(content = JsonInclude.Include.NON_EMPTY) public List items = new ArrayList(); public NonEmptyListBean add(String value) { @@ -62,7 +61,7 @@ public NonEmptyListBean add(String value) { // Test NON_DEFAULT content inclusion static class NonDefaultListBean { - @JsonInclude(content=JsonInclude.Include.NON_DEFAULT) + @JsonInclude(content = JsonInclude.Include.NON_DEFAULT) public List items = new ArrayList(); public NonDefaultListBean add(String value) { @@ -73,8 +72,8 @@ public NonDefaultListBean add(String value) { // Test with different collection types static class FooSetBean { - @JsonInclude(content=JsonInclude.Include.CUSTOM, - contentFilter=FooFilter.class) + @JsonInclude(content = JsonInclude.Include.CUSTOM, + contentFilter = FooFilter.class) public Set items = new LinkedHashSet(); public FooSetBean add(String value) { @@ -95,8 +94,8 @@ public boolean equals(Object other) { } static class NumberListBean { - @JsonInclude(content=JsonInclude.Include.CUSTOM, - contentFilter=NumberFilter.class) + @JsonInclude(content = JsonInclude.Include.CUSTOM, + contentFilter = NumberFilter.class) public List numbers = new ArrayList(); public NumberListBean add(Integer value) { @@ -117,8 +116,8 @@ public boolean equals(Object other) { } static class CountingFooListBean { - @JsonInclude(content=JsonInclude.Include.CUSTOM, - contentFilter=CountingFooFilter.class) + @JsonInclude(content = JsonInclude.Include.CUSTOM, + contentFilter = CountingFooFilter.class) public List items = new ArrayList(); public CountingFooListBean add(String value) { @@ -136,8 +135,7 @@ public CountingFooListBean add(String value) { final private ObjectMapper MAPPER = new ObjectMapper(); @Test - public void testCustomFilterWithList() throws Exception - { + public void testCustomFilterWithList() throws Exception { FooListBean input = new FooListBean() .add("1") .add("foo") @@ -147,8 +145,7 @@ public void testCustomFilterWithList() throws Exception } @Test - public void testNonNullContentInclusion() throws Exception - { + public void testNonNullContentInclusion() throws Exception { NonNullListBean input = new NonNullListBean() .add("1") .add(null) @@ -158,8 +155,7 @@ public void testNonNullContentInclusion() throws Exception } @Test - public void testNonEmptyContentInclusion() throws Exception - { + public void testNonEmptyContentInclusion() throws Exception { NonEmptyListBean input = new NonEmptyListBean() .add("1") .add("") @@ -169,8 +165,7 @@ public void testNonEmptyContentInclusion() throws Exception } @Test - public void testNonDefaultContentInclusion() throws Exception - { + public void testNonDefaultContentInclusion() throws Exception { NonDefaultListBean input = new NonDefaultListBean() .add("1") .add(null) // null is default for String @@ -180,8 +175,7 @@ public void testNonDefaultContentInclusion() throws Exception } @Test - public void testCustomFilterWithSet() throws Exception - { + public void testCustomFilterWithSet() throws Exception { FooSetBean input = new FooSetBean() .add("1") .add("foo") @@ -191,8 +185,7 @@ public void testCustomFilterWithSet() throws Exception } @Test - public void testCustomFilterWithNumbers() throws Exception - { + public void testCustomFilterWithNumbers() throws Exception { NumberListBean input = new NumberListBean() .add(1) .add(42) @@ -202,32 +195,13 @@ public void testCustomFilterWithNumbers() throws Exception } @Test - public void testCountingFilterCalls() throws Exception - { - CountingFooFilter.counter.set(0); - - CountingFooListBean input = new CountingFooListBean() - .add("1") - .add("foo") - .add("2") - .add("foo") - .add("3"); - - assertEquals(a2q("{'items':['1','2','3']}"), MAPPER.writeValueAsString(input)); - // Should be called once for each element - assertEquals(5, CountingFooFilter.counter.get()); - } - - @Test - public void testEmptyListWithCustomFilter() throws Exception - { + public void testEmptyListWithCustomFilter() throws Exception { FooListBean input = new FooListBean(); assertEquals(a2q("{'items':[]}"), MAPPER.writeValueAsString(input)); } @Test - public void testAllFilteredOut() throws Exception - { + public void testAllFilteredOut() throws Exception { FooListBean input = new FooListBean() .add("foo") .add("foo") @@ -237,8 +211,7 @@ public void testAllFilteredOut() throws Exception } @Test - public void testMixedNullsAndFiltered() throws Exception - { + public void testMixedNullsAndFiltered() throws Exception { FooListBean input = new FooListBean() .add("1") .add(null) From a2f160c9dd994bb298b20487cd2d83fdb9fbf986 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 10 Aug 2025 23:46:30 +0900 Subject: [PATCH 05/10] Minimize 5227 --- .../ser/impl/IndexedListSerializer.java | 33 ++++-------------- .../ser/impl/IndexedStringListSerializer.java | 11 ++---- .../databind/ser/impl/IteratorSerializer.java | 22 +++--------- .../ser/impl/StringCollectionSerializer.java | 14 ++------ .../ser/std/AsArraySerializerBase.java | 34 +++++++++++++++++-- .../ser/std/CollectionSerializer.java | 16 --------- .../ser/std/StaticListSerializerBase.java | 30 ++++++++++++++++ 7 files changed, 77 insertions(+), 83 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java index 81c2de4755..15b49d9a2e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java @@ -184,15 +184,8 @@ public void serializeFilteredContents(List value, JsonGenerator g, Serializer serializers = _dynamicSerializers; } // Check if this element should be suppressed - if (_suppressableValue != null) { - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty values using serializer - if (serializer.isEmpty(provider, elem)) { - continue; // Skip empty elements - } - } else if (_suppressableValue.equals(elem)) { - continue; // Skip this element - } + if (!_shouldSerializeElement(elem, serializer, provider)) { + continue; } serializer.serialize(elem, g, provider); } @@ -247,15 +240,8 @@ public void serializeFilteredContentsUsing(List value, JsonGenerator jgen, Se provider.defaultSerializeNull(jgen); } else { // Check if this element should be suppressed - if (_suppressableValue != null) { - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty values using serializer - if (ser.isEmpty(provider, elem)) { - continue; // Skip empty elements - } - } else if (_suppressableValue.equals(elem)) { - continue; // Skip this element - } + if (!_shouldSerializeElement(elem, ser, provider)) { + continue; } if (typeSer == null) { ser.serialize(elem, jgen, provider); @@ -338,15 +324,8 @@ public void serializeFilteredTypedContents(List value, JsonGenerator jgen, Se serializers = _dynamicSerializers; } // Check if this element should be suppressed - if (_suppressableValue != null) { - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty values using serializer - if (serializer.isEmpty(provider, elem)) { - continue; // Skip empty elements - } - } else if (_suppressableValue.equals(elem)) { - continue; // Skip this element - } + if (!_shouldSerializeElement(elem, serializer, provider)) { + continue; } serializer.serializeWithType(elem, jgen, provider, typeSer); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java index 5441f988d9..e8a5d2bba6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java @@ -141,15 +141,8 @@ private final void serializeFilteredContents(List value, JsonGenerator g provider.defaultSerializeNull(g); } else { // Check if this element should be suppressed - if (_suppressableValue != null) { - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty strings when NON_EMPTY is used - if (str.isEmpty()) { - continue; // Skip empty strings - } - } else if (_suppressableValue.equals(str)) { - continue; // Skip this element - } + if (!_shouldSerializeElement(str, null, provider)) { + continue; } g.writeString(str); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java index 7801bc9e08..8ed4c0d347 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java @@ -135,15 +135,8 @@ public void serializeFilteredContents(Iterator value, JsonGenerator g, provider.defaultSerializeNull(g); } else { // Check if this element should be suppressed - if (_suppressableValue != null) { - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty values using serializer - if (serializer.isEmpty(provider, elem)) { - continue; // Skip empty elements - } - } else if (_suppressableValue.equals(elem)) { - continue; // Skip this element - } + if (!_shouldSerializeElement(elem, serializer, provider)) { + continue; } if (typeSer == null) { serializer.serialize(elem, g, provider); @@ -210,15 +203,8 @@ protected void _serializeFilteredDynamicContents(Iterator value, JsonGenerato serializers = _dynamicSerializers; } // Check if this element should be suppressed - if (_suppressableValue != null) { - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty values using serializer - if (serializer.isEmpty(provider, elem)) { - continue; // Skip empty elements - } - } else if (_suppressableValue.equals(elem)) { - continue; // Skip this element - } + if (!_shouldSerializeElement(elem, serializer, provider)) { + continue; } if (typeSer == null) { serializer.serialize(elem, g, provider); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java index 06965c6b82..cc73a8bd4c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java @@ -148,17 +148,9 @@ private final void serializeFilteredContents(Collection value, JsonGener provider.defaultSerializeNull(g); } else { // Check if this element should be suppressed - if (_suppressableValue != null) { - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty strings when NON_EMPTY is used - if (str.isEmpty()) { - ++i; - continue; // Skip empty strings - } - } else if (_suppressableValue.equals(str)) { - ++i; - continue; // Skip this element - } + if (!_shouldSerializeElement(str, null, provider)) { + ++i; + continue; } g.writeString(str); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java index 87179257d2..e76e43c403 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java @@ -6,8 +6,8 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; - -import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.WritableTypeId; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; @@ -426,4 +426,34 @@ protected final JsonSerializer _findAndAddDynamic(PropertySerializerMap } return result.serializer; } + + /** + * Common utility method for checking if an element should be filtered/suppressed + * based on @JsonInclude settings. Returns {@code true} if element should be serialized, + * {@code false} if it should be skipped. + * + * @param elem Element to check for suppression + * @param serializer Serializer for the element (may be null for strings) + * @param provider Serializer provider + * @return true if element should be serialized, false if suppressed + * + * @since 2.21 + */ + protected final boolean _shouldSerializeElement(Object elem, JsonSerializer serializer, + SerializerProvider provider) throws IOException + { + if (_suppressableValue == null) { + return true; + } + if (_suppressableValue == MARKER_FOR_EMPTY) { + if (serializer != null) { + return !serializer.isEmpty(provider, elem); + } else { + // For strings and primitives, check emptiness directly + return elem instanceof String ? !((String) elem).isEmpty() : true; + } + } else { + return !_suppressableValue.equals(elem); + } + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index cbb5f63f29..8c62eb24ba 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -305,20 +305,4 @@ public void serializeFilteredContentsUsing(Collection value, JsonGenerator g, } } - /** - * Helper method to determine if an element should be serialized based on content inclusion filters. - */ - private boolean _shouldSerializeElement(Object elem, JsonSerializer serializer, SerializerProvider provider) throws IOException - { - if (_suppressableValue == null) { - return true; - } - if (_suppressableValue == MARKER_FOR_EMPTY) { - // Check for empty values using serializer - return !serializer.isEmpty(provider, elem); - } else { - // Check for custom filter match - return !_suppressableValue.equals(elem); - } - } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java index 8e22301a5d..5495f9f8d2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java @@ -225,4 +225,34 @@ protected abstract void acceptContentVisitor(JsonArrayFormatVisitor visitor) @Override public abstract void serializeWithType(T value, JsonGenerator g, SerializerProvider provider, TypeSerializer typeSer) throws IOException; + + /** + * Common utility method for checking if an element should be filtered/suppressed + * based on @JsonInclude settings. Returns {@code true} if element should be serialized, + * {@code false} if it should be skipped. + * + * @param elem Element to check for suppression + * @param serializer Serializer for the element (may be null for strings) + * @param provider Serializer provider + * @return true if element should be serialized, false if suppressed + * + * @since 2.21 + */ + protected final boolean _shouldSerializeElement(Object elem, JsonSerializer serializer, + SerializerProvider provider) throws IOException + { + if (_suppressableValue == null) { + return true; + } + if (_suppressableValue == MARKER_FOR_EMPTY) { + if (serializer != null) { + return !serializer.isEmpty(provider, elem); + } else { + // For strings and primitives, check emptiness directly + return elem instanceof String ? !((String) elem).isEmpty() : true; + } + } else { + return !_suppressableValue.equals(elem); + } + } } From 5c965ec4a130597524074f3efd8839b39d27f5b2 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 15 Aug 2025 21:44:58 +0900 Subject: [PATCH 06/10] Refactor to minimze code --- .../ser/impl/IndexedListSerializer.java | 63 +++++---------- .../ser/impl/IndexedStringListSerializer.java | 26 +++---- .../databind/ser/impl/IteratorSerializer.java | 76 ++++++------------- .../ser/impl/StringCollectionSerializer.java | 28 +++---- .../ser/std/AsArraySerializerBase.java | 4 + .../ser/std/CollectionSerializer.java | 62 ++++----------- 6 files changed, 85 insertions(+), 174 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java index 15b49d9a2e..58a65986e7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java @@ -105,55 +105,32 @@ public final void serialize(List value, JsonGenerator gen, SerializerProvider public void serializeContents(List value, JsonGenerator g, SerializerProvider provider) throws IOException { - if (_elementSerializer != null) { - serializeContentsUsing(value, g, provider, _elementSerializer); - return; - } - if (_valueTypeSerializer != null) { - serializeTypedContents(value, g, provider); - return; - } - final int len = value.size(); - if (len == 0) { - return; - } - int i = 0; - try { - PropertySerializerMap serializers = _dynamicSerializers; - for (; i < len; ++i) { - Object elem = value.get(i); - if (elem == null) { - provider.defaultSerializeNull(g); - } else { - Class cc = elem.getClass(); - JsonSerializer serializer = serializers.serializerFor(cc); - if (serializer == null) { - // To fix [JACKSON-508] - if (_elementType.hasGenericTypes()) { - serializer = _findAndAddDynamic(serializers, - provider.constructSpecializedType(_elementType, cc), provider); - } else { - serializer = _findAndAddDynamic(serializers, cc, provider); - } - serializers = _dynamicSerializers; - } - serializer.serialize(elem, g, provider); - } - } - } catch (Exception e) { - wrapAndThrow(provider, e, value, i); - } + serializeContentsImpl(value, g, provider, false); } public void serializeFilteredContents(List value, JsonGenerator g, SerializerProvider provider) throws IOException + { + serializeContentsImpl(value, g, provider, true); + } + + private void serializeContentsImpl(List value, JsonGenerator g, SerializerProvider provider, boolean filtered) + throws IOException { if (_elementSerializer != null) { - serializeFilteredContentsUsing(value, g, provider, _elementSerializer); + if (filtered) { + serializeFilteredContentsUsing(value, g, provider, _elementSerializer); + } else { + serializeContentsUsing(value, g, provider, _elementSerializer); + } return; } if (_valueTypeSerializer != null) { - serializeFilteredTypedContents(value, g, provider); + if (filtered) { + serializeFilteredTypedContents(value, g, provider); + } else { + serializeTypedContents(value, g, provider); + } return; } final int len = value.size(); @@ -166,7 +143,7 @@ public void serializeFilteredContents(List value, JsonGenerator g, Serializer for (; i < len; ++i) { Object elem = value.get(i); if (elem == null) { - if (_suppressNulls) { + if (filtered && _suppressNulls) { continue; } provider.defaultSerializeNull(g); @@ -183,8 +160,8 @@ public void serializeFilteredContents(List value, JsonGenerator g, Serializer } serializers = _dynamicSerializers; } - // Check if this element should be suppressed - if (!_shouldSerializeElement(elem, serializer, provider)) { + // Check if this element should be suppressed (only in filtered mode) + if (filtered && !_shouldSerializeElement(elem, serializer, provider)) { continue; } serializer.serialize(elem, g, provider); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java index e8a5d2bba6..671e25ae41 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedStringListSerializer.java @@ -112,36 +112,30 @@ public void serializeWithType(List value, JsonGenerator g, SerializerPro private final void serializeContents(List value, JsonGenerator g, SerializerProvider provider, int len) throws IOException { - int i = 0; - try { - for (; i < len; ++i) { - String str = value.get(i); - if (str == null) { - provider.defaultSerializeNull(g); - } else { - g.writeString(str); - } - } - } catch (Exception e) { - wrapAndThrow(provider, e, value, i); - } + serializeContentsImpl(value, g, provider, len, false); } private final void serializeFilteredContents(List value, JsonGenerator g, SerializerProvider provider, int len) throws IOException + { + serializeContentsImpl(value, g, provider, len, true); + } + + private final void serializeContentsImpl(List value, JsonGenerator g, + SerializerProvider provider, int len, boolean filtered) throws IOException { int i = 0; try { for (; i < len; ++i) { String str = value.get(i); if (str == null) { - if (_suppressNulls) { + if (filtered && _suppressNulls) { continue; } provider.defaultSerializeNull(g); } else { - // Check if this element should be suppressed - if (!_shouldSerializeElement(str, null, provider)) { + // Check if this element should be suppressed (only in filtered mode) + if (filtered && !_shouldSerializeElement(str, null, provider)) { continue; } g.writeString(str); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java index 8ed4c0d347..fa3cbd4747 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java @@ -93,49 +93,41 @@ public final void serialize(Iterator value, JsonGenerator gen, public void serializeContents(Iterator value, JsonGenerator g, SerializerProvider provider) throws IOException { - if (!value.hasNext()) { - return; - } - JsonSerializer serializer = _elementSerializer; - if (serializer == null) { - _serializeDynamicContents(value, g, provider); - return; - } - final TypeSerializer typeSer = _valueTypeSerializer; - do { - Object elem = value.next(); - if (elem == null) { - provider.defaultSerializeNull(g); - } else if (typeSer == null) { - serializer.serialize(elem, g, provider); - } else { - serializer.serializeWithType(elem, g, provider, typeSer); - } - } while (value.hasNext()); + serializeContentsImpl(value, g, provider, false); } public void serializeFilteredContents(Iterator value, JsonGenerator g, SerializerProvider provider) throws IOException + { + serializeContentsImpl(value, g, provider, true); + } + + private void serializeContentsImpl(Iterator value, JsonGenerator g, + SerializerProvider provider, boolean filtered) throws IOException { if (!value.hasNext()) { return; } JsonSerializer serializer = _elementSerializer; if (serializer == null) { - _serializeFilteredDynamicContents(value, g, provider); + if (filtered) { + _serializeFilteredDynamicContents(value, g, provider); + } else { + _serializeDynamicContents(value, g, provider); + } return; } final TypeSerializer typeSer = _valueTypeSerializer; do { Object elem = value.next(); if (elem == null) { - if (_suppressNulls) { + if (filtered && _suppressNulls) { continue; } provider.defaultSerializeNull(g); } else { - // Check if this element should be suppressed - if (!_shouldSerializeElement(elem, serializer, provider)) { + // Check if this element should be suppressed (only in filtered mode) + if (filtered && !_shouldSerializeElement(elem, serializer, provider)) { continue; } if (typeSer == null) { @@ -150,42 +142,24 @@ public void serializeFilteredContents(Iterator value, JsonGenerator g, protected void _serializeDynamicContents(Iterator value, JsonGenerator g, SerializerProvider provider) throws IOException { - final TypeSerializer typeSer = _valueTypeSerializer; - PropertySerializerMap serializers = _dynamicSerializers; - do { - Object elem = value.next(); - if (elem == null) { - provider.defaultSerializeNull(g); - continue; - } - Class cc = elem.getClass(); - JsonSerializer serializer = serializers.serializerFor(cc); - if (serializer == null) { - if (_elementType.hasGenericTypes()) { - serializer = _findAndAddDynamic(serializers, - provider.constructSpecializedType(_elementType, cc), provider); - } else { - serializer = _findAndAddDynamic(serializers, cc, provider); - } - serializers = _dynamicSerializers; - } - if (typeSer == null) { - serializer.serialize(elem, g, provider); - } else { - serializer.serializeWithType(elem, g, provider, typeSer); - } - } while (value.hasNext()); + _serializeDynamicContentsImpl(value, g, provider, false); } protected void _serializeFilteredDynamicContents(Iterator value, JsonGenerator g, SerializerProvider provider) throws IOException + { + _serializeDynamicContentsImpl(value, g, provider, true); + } + + private void _serializeDynamicContentsImpl(Iterator value, JsonGenerator g, + SerializerProvider provider, boolean filtered) throws IOException { final TypeSerializer typeSer = _valueTypeSerializer; PropertySerializerMap serializers = _dynamicSerializers; do { Object elem = value.next(); if (elem == null) { - if (_suppressNulls) { + if (filtered && _suppressNulls) { continue; } provider.defaultSerializeNull(g); @@ -202,8 +176,8 @@ protected void _serializeFilteredDynamicContents(Iterator value, JsonGenerato } serializers = _dynamicSerializers; } - // Check if this element should be suppressed - if (!_shouldSerializeElement(elem, serializer, provider)) { + // Check if this element should be suppressed (only in filtered mode) + if (filtered && !_shouldSerializeElement(elem, serializer, provider)) { continue; } if (typeSer == null) { diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java index cc73a8bd4c..c9eac28dba 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringCollectionSerializer.java @@ -116,39 +116,33 @@ private final void serializeContents(Collection value, JsonGenerator g, SerializerProvider provider) throws IOException { - int i = 0; - - try { - for (String str : value) { - if (str == null) { - provider.defaultSerializeNull(g); - } else { - g.writeString(str); - } - ++i; - } - } catch (Exception e) { - wrapAndThrow(provider, e, value, i); - } + serializeContentsImpl(value, g, provider, false); } private final void serializeFilteredContents(Collection value, JsonGenerator g, SerializerProvider provider) throws IOException + { + serializeContentsImpl(value, g, provider, true); + } + + private final void serializeContentsImpl(Collection value, JsonGenerator g, + SerializerProvider provider, boolean filtered) + throws IOException { int i = 0; try { for (String str : value) { if (str == null) { - if (_suppressNulls) { + if (filtered && _suppressNulls) { ++i; continue; } provider.defaultSerializeNull(g); } else { - // Check if this element should be suppressed - if (!_shouldSerializeElement(str, null, provider)) { + // Check if this element should be suppressed (only in filtered mode) + if (filtered && !_shouldSerializeElement(str, null, provider)) { ++i; continue; } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java index e76e43c403..3344d8655f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.lang.reflect.Type; +import java.util.Collection; import java.util.Objects; import com.fasterxml.jackson.annotation.JsonFormat; @@ -368,6 +369,9 @@ public void serializeWithType(T value, JsonGenerator g, SerializerProvider provi protected abstract void serializeContents(T value, JsonGenerator gen, SerializerProvider provider) throws IOException; + protected abstract void serializeFilteredContents(Collection value, JsonGenerator g, SerializerProvider provider) + throws IOException; + /** * @deprecated Since 2.15 */ diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index 8c62eb24ba..6cca80457b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -137,56 +137,24 @@ public final void serialize(Collection value, JsonGenerator g, SerializerProv @Override public void serializeContents(Collection value, JsonGenerator g, SerializerProvider provider) throws IOException { - g.assignCurrentValue(value); - if (_elementSerializer != null) { - serializeContentsUsing(value, g, provider, _elementSerializer); - return; - } - Iterator it = value.iterator(); - if (!it.hasNext()) { - return; - } - PropertySerializerMap serializers = _dynamicSerializers; - // [databind#4849]/[databind#4214]: need to check for EnumSet - final TypeSerializer typeSer = (_maybeEnumSet && value instanceof EnumSet) - ? null : _valueTypeSerializer; - - int i = 0; - try { - do { - Object elem = it.next(); - if (elem == null) { - provider.defaultSerializeNull(g); - } else { - Class cc = elem.getClass(); - JsonSerializer serializer = serializers.serializerFor(cc); - if (serializer == null) { - if (_elementType.hasGenericTypes()) { - serializer = _findAndAddDynamic(serializers, - provider.constructSpecializedType(_elementType, cc), provider); - } else { - serializer = _findAndAddDynamic(serializers, cc, provider); - } - serializers = _dynamicSerializers; - } - if (typeSer == null) { - serializer.serialize(elem, g, provider); - } else { - serializer.serializeWithType(elem, g, provider, typeSer); - } - } - ++i; - } while (it.hasNext()); - } catch (Exception e) { - wrapAndThrow(provider, e, value, i); - } + serializeContentsImpl(value, g, provider, false); } + @Override public void serializeFilteredContents(Collection value, JsonGenerator g, SerializerProvider provider) throws IOException + { + serializeContentsImpl(value, g, provider, true); + } + + private void serializeContentsImpl(Collection value, JsonGenerator g, SerializerProvider provider, boolean filtered) throws IOException { g.assignCurrentValue(value); if (_elementSerializer != null) { - serializeFilteredContentsUsing(value, g, provider, _elementSerializer); + if (filtered) { + serializeFilteredContentsUsing(value, g, provider, _elementSerializer); + } else { + serializeContentsUsing(value, g, provider, _elementSerializer); + } return; } Iterator it = value.iterator(); @@ -203,7 +171,7 @@ public void serializeFilteredContents(Collection value, JsonGenerator g, Seri do { Object elem = it.next(); if (elem == null) { - if (_suppressNulls) { + if (filtered && _suppressNulls) { ++i; continue; } @@ -220,8 +188,8 @@ public void serializeFilteredContents(Collection value, JsonGenerator g, Seri } serializers = _dynamicSerializers; } - // Check if this element should be suppressed - if (!_shouldSerializeElement(elem, serializer, provider)) { + // Check if this element should be suppressed (only in filtered mode) + if (filtered && !_shouldSerializeElement(elem, serializer, provider)) { ++i; continue; } From af5921704bdbb5d5517d55121475ea0d301f224a Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 15 Aug 2025 21:58:33 +0900 Subject: [PATCH 07/10] .. --- .../jackson/databind/ser/impl/IndexedListSerializer.java | 4 ++-- .../jackson/databind/ser/impl/IteratorSerializer.java | 5 +++-- .../jackson/databind/ser/std/AsArraySerializerBase.java | 2 +- .../jackson/databind/ser/std/CollectionSerializer.java | 2 +- .../jackson/databind/ser/std/EnumSetSerializer.java | 7 +++++++ .../jackson/databind/ser/std/IterableSerializer.java | 6 ++++++ 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java index 58a65986e7..255fa1266d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java @@ -108,8 +108,8 @@ public void serializeContents(List value, JsonGenerator g, SerializerProvider serializeContentsImpl(value, g, provider, false); } - public void serializeFilteredContents(List value, JsonGenerator g, SerializerProvider provider) - throws IOException + @Override + protected void serializeFilteredContents(List value, JsonGenerator g, SerializerProvider provider) throws IOException { serializeContentsImpl(value, g, provider, true); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java index fa3cbd4747..7f09ceef2a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IteratorSerializer.java @@ -1,6 +1,7 @@ package com.fasterxml.jackson.databind.ser.impl; import java.io.IOException; +import java.util.Collection; import java.util.Iterator; import com.fasterxml.jackson.core.JsonGenerator; @@ -96,8 +97,8 @@ public void serializeContents(Iterator value, JsonGenerator g, serializeContentsImpl(value, g, provider, false); } - public void serializeFilteredContents(Iterator value, JsonGenerator g, - SerializerProvider provider) throws IOException + @Override + protected void serializeFilteredContents(Iterator value, JsonGenerator g, SerializerProvider provider) throws IOException { serializeContentsImpl(value, g, provider, true); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java index 3344d8655f..83ad450bc0 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/AsArraySerializerBase.java @@ -369,7 +369,7 @@ public void serializeWithType(T value, JsonGenerator g, SerializerProvider provi protected abstract void serializeContents(T value, JsonGenerator gen, SerializerProvider provider) throws IOException; - protected abstract void serializeFilteredContents(Collection value, JsonGenerator g, SerializerProvider provider) + protected abstract void serializeFilteredContents(T value, JsonGenerator g, SerializerProvider provider) throws IOException; /** diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index 6cca80457b..d09d1b28b2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -141,7 +141,7 @@ public void serializeContents(Collection value, JsonGenerator g, SerializerPr } @Override - public void serializeFilteredContents(Collection value, JsonGenerator g, SerializerProvider provider) throws IOException + protected void serializeFilteredContents(Collection value, JsonGenerator g, SerializerProvider provider) throws IOException { serializeContentsImpl(value, g, provider, true); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java index be8c9e4bf6..e878703fa7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java @@ -95,4 +95,11 @@ public void serializeContents(EnumSet> value, JsonGenerator ge enumSer.serialize(en, gen, provider); } } + + @Override + protected void serializeFilteredContents(EnumSet> value, JsonGenerator g, SerializerProvider provider) throws IOException { + // TODO: Implement, later? + serializeContents(value, g, provider); + } + } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java index 32bade29cc..b83c72de5a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/IterableSerializer.java @@ -122,4 +122,10 @@ public void serializeContents(Iterable value, JsonGenerator jgen, } while (it.hasNext()); } } + + @Override + protected void serializeFilteredContents(Iterable value, JsonGenerator g, SerializerProvider provider) throws IOException { + // TODO: Implement, later? + serializeContents(value, g, provider); + } } From c239a75519476aa484c8934dcdabfa3e619fa0c1 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 15 Aug 2025 22:02:47 +0900 Subject: [PATCH 08/10] Refactor --- .../ser/impl/IndexedListSerializer.java | 78 +++++-------------- 1 file changed, 21 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java index 255fa1266d..663fe4c775 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java @@ -176,31 +176,19 @@ public void serializeContentsUsing(List value, JsonGenerator jgen, Serializer JsonSerializer ser) throws IOException { - final int len = value.size(); - if (len == 0) { - return; - } - final TypeSerializer typeSer = _valueTypeSerializer; - for (int i = 0; i < len; ++i) { - Object elem = value.get(i); - try { - if (elem == null) { - provider.defaultSerializeNull(jgen); - } else if (typeSer == null) { - ser.serialize(elem, jgen, provider); - } else { - ser.serializeWithType(elem, jgen, provider, typeSer); - } - } catch (Exception e) { - // [JACKSON-55] Need to add reference information - wrapAndThrow(provider, e, value, i); - } - } + serializeContentsUsingImpl(value, jgen, provider, ser, false); } public void serializeFilteredContentsUsing(List value, JsonGenerator jgen, SerializerProvider provider, JsonSerializer ser) throws IOException + { + serializeContentsUsingImpl(value, jgen, provider, ser, true); + } + + private void serializeContentsUsingImpl(List value, JsonGenerator jgen, SerializerProvider provider, + JsonSerializer ser, boolean filtered) + throws IOException { final int len = value.size(); if (len == 0) { @@ -211,13 +199,13 @@ public void serializeFilteredContentsUsing(List value, JsonGenerator jgen, Se Object elem = value.get(i); try { if (elem == null) { - if (_suppressNulls) { + if (filtered && _suppressNulls) { continue; } provider.defaultSerializeNull(jgen); } else { - // Check if this element should be suppressed - if (!_shouldSerializeElement(elem, ser, provider)) { + // Check if this element should be suppressed (only in filtered mode) + if (filtered && !_shouldSerializeElement(elem, ser, provider)) { continue; } if (typeSer == null) { @@ -236,41 +224,17 @@ public void serializeFilteredContentsUsing(List value, JsonGenerator jgen, Se public void serializeTypedContents(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - final int len = value.size(); - if (len == 0) { - return; - } - int i = 0; - try { - final TypeSerializer typeSer = _valueTypeSerializer; - PropertySerializerMap serializers = _dynamicSerializers; - for (; i < len; ++i) { - Object elem = value.get(i); - if (elem == null) { - provider.defaultSerializeNull(jgen); - } else { - Class cc = elem.getClass(); - JsonSerializer serializer = serializers.serializerFor(cc); - if (serializer == null) { - // To fix [JACKSON-508] - if (_elementType.hasGenericTypes()) { - serializer = _findAndAddDynamic(serializers, - provider.constructSpecializedType(_elementType, cc), provider); - } else { - serializer = _findAndAddDynamic(serializers, cc, provider); - } - serializers = _dynamicSerializers; - } - serializer.serializeWithType(elem, jgen, provider, typeSer); - } - } - } catch (Exception e) { - wrapAndThrow(provider, e, value, i); - } + serializeTypedContentsImpl(value, jgen, provider, false); } public void serializeFilteredTypedContents(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException + { + serializeTypedContentsImpl(value, jgen, provider, true); + } + + private void serializeTypedContentsImpl(List value, JsonGenerator jgen, SerializerProvider provider, boolean filtered) + throws IOException { final int len = value.size(); if (len == 0) { @@ -283,7 +247,7 @@ public void serializeFilteredTypedContents(List value, JsonGenerator jgen, Se for (; i < len; ++i) { Object elem = value.get(i); if (elem == null) { - if (_suppressNulls) { + if (filtered && _suppressNulls) { continue; } provider.defaultSerializeNull(jgen); @@ -300,8 +264,8 @@ public void serializeFilteredTypedContents(List value, JsonGenerator jgen, Se } serializers = _dynamicSerializers; } - // Check if this element should be suppressed - if (!_shouldSerializeElement(elem, serializer, provider)) { + // Check if this element should be suppressed (only in filtered mode) + if (filtered && !_shouldSerializeElement(elem, serializer, provider)) { continue; } serializer.serializeWithType(elem, jgen, provider, typeSer); From 4881815e705658464043fbdc963113a124991751 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 15 Aug 2025 22:09:23 +0900 Subject: [PATCH 09/10] Clean up CollectionSerializer as well --- .../ser/std/CollectionSerializer.java | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index d09d1b28b2..17546d0ab6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -209,34 +209,17 @@ private void serializeContentsImpl(Collection value, JsonGenerator g, Seriali public void serializeContentsUsing(Collection value, JsonGenerator g, SerializerProvider provider, JsonSerializer ser) throws IOException { - Iterator it = value.iterator(); - if (it.hasNext()) { - // [databind#4849]/[databind#4214]: need to check for EnumSet - final TypeSerializer typeSer = (_maybeEnumSet && value instanceof EnumSet) - ? null : _valueTypeSerializer; - int i = 0; - do { - Object elem = it.next(); - try { - if (elem == null) { - provider.defaultSerializeNull(g); - } else { - if (typeSer == null) { - ser.serialize(elem, g, provider); - } else { - ser.serializeWithType(elem, g, provider, typeSer); - } - } - ++i; - } catch (Exception e) { - wrapAndThrow(provider, e, value, i); - } - } while (it.hasNext()); - } + serializeContentsUsingImpl(value, g, provider, ser, false); } public void serializeFilteredContentsUsing(Collection value, JsonGenerator g, SerializerProvider provider, JsonSerializer ser) throws IOException + { + serializeContentsUsingImpl(value, g, provider, ser, true); + } + + private void serializeContentsUsingImpl(Collection value, JsonGenerator g, SerializerProvider provider, + JsonSerializer ser, boolean filtered) throws IOException { Iterator it = value.iterator(); if (it.hasNext()) { @@ -248,14 +231,14 @@ public void serializeFilteredContentsUsing(Collection value, JsonGenerator g, Object elem = it.next(); try { if (elem == null) { - if (_suppressNulls) { + if (filtered && _suppressNulls) { ++i; continue; } provider.defaultSerializeNull(g); } else { - // Check if this element should be suppressed - if (!_shouldSerializeElement(elem, ser, provider)) { + // Check if this element should be suppressed (only in filtered mode) + if (filtered && !_shouldSerializeElement(elem, ser, provider)) { ++i; continue; } From d864eb93f4b90e7b1ab8f04af783e51c8a392a9b Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 15 Aug 2025 22:22:36 +0900 Subject: [PATCH 10/10] FIx visibility --- .../jackson/databind/ser/impl/IndexedListSerializer.java | 8 ++++---- .../jackson/databind/ser/std/CollectionSerializer.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java index 663fe4c775..8532f8388f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/IndexedListSerializer.java @@ -172,14 +172,14 @@ private void serializeContentsImpl(List value, JsonGenerator g, SerializerPro } } - public void serializeContentsUsing(List value, JsonGenerator jgen, SerializerProvider provider, + private void serializeContentsUsing(List value, JsonGenerator jgen, SerializerProvider provider, JsonSerializer ser) throws IOException { serializeContentsUsingImpl(value, jgen, provider, ser, false); } - public void serializeFilteredContentsUsing(List value, JsonGenerator jgen, SerializerProvider provider, + private void serializeFilteredContentsUsing(List value, JsonGenerator jgen, SerializerProvider provider, JsonSerializer ser) throws IOException { @@ -221,13 +221,13 @@ private void serializeContentsUsingImpl(List value, JsonGenerator jgen, Seria } } - public void serializeTypedContents(List value, JsonGenerator jgen, SerializerProvider provider) + private void serializeTypedContents(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException { serializeTypedContentsImpl(value, jgen, provider, false); } - public void serializeFilteredTypedContents(List value, JsonGenerator jgen, SerializerProvider provider) + private void serializeFilteredTypedContents(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException { serializeTypedContentsImpl(value, jgen, provider, true); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index 17546d0ab6..5f2de3bf6a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -212,7 +212,7 @@ public void serializeContentsUsing(Collection value, JsonGenerator g, Seriali serializeContentsUsingImpl(value, g, provider, ser, false); } - public void serializeFilteredContentsUsing(Collection value, JsonGenerator g, SerializerProvider provider, + private void serializeFilteredContentsUsing(Collection value, JsonGenerator g, SerializerProvider provider, JsonSerializer ser) throws IOException { serializeContentsUsingImpl(value, g, provider, ser, true);