diff --git a/src/main/java/tools/jackson/databind/SerializationFeature.java b/src/main/java/tools/jackson/databind/SerializationFeature.java
index e778426c9b..e5c30c0f4a 100644
--- a/src/main/java/tools/jackson/databind/SerializationFeature.java
+++ b/src/main/java/tools/jackson/databind/SerializationFeature.java
@@ -256,6 +256,24 @@ public enum SerializationFeature implements ConfigFeature
*/
FAIL_ON_ORDER_MAP_BY_INCOMPARABLE_KEY(false),
+ /**
+ * Feature that determines whether {@code JsonInclude#content()} is also
+ * applied to values inside {@link java.util.Collection} properties.
+ *
+ * By default, {@code content()} inclusion rules are only applied to
+ * {@code Map} values and reference types, and are ignored for
+ * {@code Collection} element values.
+ *
+ * When this feature is enabled, {@code JsonInclude#content()} rules
+ * are evaluated for {@code Collection} elements during serialization.
+ *
+ * This feature is disabled by default for backwards
+ * compatibility.
+ *
+ * @since 3.1
+ */
+ APPLY_JSON_INCLUDE_FOR_COLLECTIONS(false),
+
/*
/**********************************************************************
/* Other
diff --git a/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java
index 065332dead..e1a30e787c 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java
@@ -47,10 +47,21 @@ public CollectionSerializer(JavaType elemType, boolean staticTyping, TypeSeriali
_maybeEnumSet = elemType.isEnumType() || elemType.isJavaLangObject();
}
+ @Deprecated // since 3.1.0
protected CollectionSerializer(CollectionSerializer src,
TypeSerializer vts, ValueSerializer> valueSerializer,
Boolean unwrapSingle, BeanProperty property) {
- super(src, vts, valueSerializer, unwrapSingle, property);
+ this(src, vts, valueSerializer, unwrapSingle, property, null, false);
+ }
+
+ /**
+ * @since 3.1.0
+ */
+ protected CollectionSerializer(CollectionSerializer src,
+ TypeSerializer vts, ValueSerializer> valueSerializer,
+ Boolean unwrapSingle, BeanProperty property,
+ Object suppressableValue, boolean suppressNulls) {
+ super(src, vts, valueSerializer, unwrapSingle, property, suppressableValue, suppressNulls);
_maybeEnumSet = src._maybeEnumSet;
}
@@ -63,7 +74,15 @@ protected StdContainerSerializer> _withValueTypeSerializer(TypeSerializer vts)
protected CollectionSerializer withResolved(BeanProperty property,
TypeSerializer vts, ValueSerializer> elementSerializer,
Boolean unwrapSingle) {
- return new CollectionSerializer(this, vts, elementSerializer, unwrapSingle, property);
+ return withResolved(property, vts, elementSerializer, unwrapSingle, null, false);
+ }
+
+ // @since 3.1.0
+ @Override
+ protected CollectionSerializer withResolved(BeanProperty property,
+ TypeSerializer vts, ValueSerializer> elementSerializer,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
+ return new CollectionSerializer(this, vts, elementSerializer, unwrapSingle, property, suppressableValue, suppressNulls);
}
/*
@@ -97,21 +116,49 @@ public final void serialize(Collection> value, JsonGenerator g, SerializationC
if (((_unwrapSingle == null) &&
provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, provider);
+ if (provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, provider);
+ } else {
+ serializeContents(value, g, provider);
+ }
return;
}
}
g.writeStartArray(value, len);
- serializeContents(value, g, provider);
+ if (provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, provider);
+ } else {
+ serializeContents(value, g, provider);
+ }
g.writeEndArray();
}
@Override
public void serializeContents(Collection> value, JsonGenerator g, SerializationContext ctxt)
- throws JacksonException
+ {
+ serializeContentsImpl(value, g, ctxt,
+ false);
+ }
+
+ @Override
+ protected void serializeFilteredContents(Collection> value, JsonGenerator g, SerializationContext ctxt) throws JacksonException
+ {
+ serializeContentsImpl(value, g, ctxt,
+ ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private void serializeContentsImpl(Collection> value, JsonGenerator g, SerializationContext ctxt, boolean filtered) throws JacksonException
{
if (_elementSerializer != null) {
- serializeContentsUsing(value, g, ctxt, _elementSerializer);
+ if (filtered) {
+ serializeFilteredContentsUsing(value, g, ctxt, _elementSerializer);
+ } else {
+ serializeContentsUsing(value, g, ctxt, _elementSerializer);
+ }
return;
}
Iterator> it = value.iterator();
@@ -128,6 +175,10 @@ public void serializeContents(Collection> value, JsonGenerator g, Serializatio
do {
Object elem = it.next();
if (elem == null) {
+ if (filtered && _suppressNulls) {
+ ++i;
+ continue;
+ }
ctxt.defaultSerializeNullValue(g);
} else {
Class> cc = elem.getClass();
@@ -140,6 +191,11 @@ public void serializeContents(Collection> value, JsonGenerator g, Serializatio
}
serializers = _dynamicValueSerializers;
}
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(elem, serializer, ctxt)) {
+ ++i;
+ continue;
+ }
if (typeSer == null) {
serializer.serialize(elem, g, ctxt);
} else {
@@ -153,9 +209,23 @@ public void serializeContents(Collection> value, JsonGenerator g, Serializatio
}
}
- public void serializeContentsUsing(Collection> value, JsonGenerator g, SerializationContext provider,
+ public void serializeContentsUsing(Collection> value, JsonGenerator g, SerializationContext ctxt,
ValueSerializer ser)
throws JacksonException
+ {
+ serializeContentsUsingImpl(value, g, ctxt, ser,
+ false);
+ }
+
+ private void serializeFilteredContentsUsing(Collection> value, JsonGenerator g, SerializationContext ctxt,
+ ValueSerializer ser) throws JacksonException
+ {
+ serializeContentsUsingImpl(value, g, ctxt, ser,
+ ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private void serializeContentsUsingImpl(Collection> value, JsonGenerator g, SerializationContext ctxt,
+ ValueSerializer ser, boolean filtered) throws JacksonException
{
Iterator> it = value.iterator();
if (it.hasNext()) {
@@ -167,17 +237,26 @@ public void serializeContentsUsing(Collection> value, JsonGenerator g, Seriali
Object elem = it.next();
try {
if (elem == null) {
- provider.defaultSerializeNullValue(g);
+ if (filtered && _suppressNulls) {
+ ++i;
+ continue;
+ }
+ ctxt.defaultSerializeNullValue(g);
} else {
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(elem, ser, ctxt)) {
+ ++i;
+ continue;
+ }
if (typeSer == null) {
- ser.serialize(elem, g, provider);
+ ser.serialize(elem, g, ctxt);
} else {
- ser.serializeWithType(elem, g, provider, typeSer);
+ ser.serializeWithType(elem, g, ctxt, typeSer);
}
}
++i;
} catch (Exception e) {
- wrapAndThrow(provider, e, value, i);
+ wrapAndThrow(ctxt, e, value, i);
}
} while (it.hasNext());
}
diff --git a/src/main/java/tools/jackson/databind/ser/jdk/EnumSetSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/EnumSetSerializer.java
index 4c2abcf8cf..159c05d553 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/EnumSetSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/EnumSetSerializer.java
@@ -14,10 +14,21 @@ public EnumSetSerializer(JavaType elemType) {
super(EnumSet.class, elemType, true, null, null);
}
+ @Deprecated // since 3.1.0
public EnumSetSerializer(EnumSetSerializer src,
TypeSerializer vts, ValueSerializer> valueSerializer,
Boolean unwrapSingle, BeanProperty property) {
- super(src, vts, valueSerializer, unwrapSingle, property);
+ this(src, vts, valueSerializer, unwrapSingle, property, null, false);
+ }
+
+ /**
+ * @since 3.1.0
+ */
+ public EnumSetSerializer(EnumSetSerializer src,
+ TypeSerializer vts, ValueSerializer> valueSerializer,
+ Boolean unwrapSingle, BeanProperty property,
+ Object suppressableValue, boolean suppressNulls) {
+ super(src, vts, valueSerializer, unwrapSingle, property, suppressableValue, suppressNulls);
}
@Override
@@ -30,7 +41,14 @@ protected EnumSetSerializer _withValueTypeSerializer(TypeSerializer vts) {
protected EnumSetSerializer withResolved(BeanProperty property,
TypeSerializer vts, ValueSerializer> elementSerializer,
Boolean unwrapSingle) {
- return new EnumSetSerializer(this, vts, elementSerializer, unwrapSingle, property);
+ return new EnumSetSerializer(this, vts, elementSerializer, unwrapSingle, property, null, false);
+ }
+
+ @Override
+ public EnumSetSerializer withResolved(BeanProperty property,
+ TypeSerializer vts, ValueSerializer> elementSerializer,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
+ return new EnumSetSerializer(this, vts, elementSerializer, unwrapSingle, property, suppressableValue, suppressNulls);
}
@Override
@@ -80,4 +98,5 @@ public void serializeContents(EnumSet extends Enum>> value, JsonGenerator ge
enumSer.serialize(en, gen, ctxt);
}
}
+
}
diff --git a/src/main/java/tools/jackson/databind/ser/jdk/IndexedListSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/IndexedListSerializer.java
index 2326acba28..27acb14ca7 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/IndexedListSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/IndexedListSerializer.java
@@ -26,10 +26,20 @@ public IndexedListSerializer(JavaType elemType, boolean staticTyping, TypeSerial
super(List.class, elemType, staticTyping, vts, valueSerializer);
}
+ @Deprecated // since 3.1.0
public IndexedListSerializer(IndexedListSerializer src,
TypeSerializer vts, ValueSerializer> valueSerializer,
Boolean unwrapSingle, BeanProperty property) {
- super(src, vts, valueSerializer, unwrapSingle, property);
+ this(src, vts, valueSerializer, unwrapSingle, property, src._suppressableValue, src._suppressNulls);
+ }
+
+ /**
+ * @since 3.1.0
+ */
+ public IndexedListSerializer(IndexedListSerializer src,
+ TypeSerializer vts, ValueSerializer> valueSerializer, Boolean unwrapSingle,
+ BeanProperty property, Object suppressableValue, boolean suppressNulls) {
+ super(src, vts, valueSerializer, unwrapSingle, property, suppressableValue, suppressNulls);
}
@Override
@@ -45,6 +55,15 @@ public IndexedListSerializer withResolved(BeanProperty property,
return new IndexedListSerializer(this, vts, elementSerializer, unwrapSingle, property);
}
+ @Override
+ public IndexedListSerializer withResolved(BeanProperty property,
+ TypeSerializer vts, ValueSerializer> elementSerializer,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
+ return new IndexedListSerializer(this, vts, elementSerializer, unwrapSingle, property,
+ suppressableValue, suppressNulls);
+ }
+
+
/*
/**********************************************************************
/* Accessors
@@ -71,26 +90,61 @@ public final void serialize(Object value0, JsonGenerator gen, SerializationConte
if (((_unwrapSingle == null) &&
provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, gen, provider);
+ if (provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, gen, provider);
+ } else {
+ serializeContents(value, gen, provider);
+ }
return;
}
}
gen.writeStartArray(value, len);
- serializeContents(value, gen, provider);
+ if (provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, gen, provider);
+ } else {
+ serializeContents(value, gen, provider);
+ }
gen.writeEndArray();
}
@Override
public void serializeContents(Object value0, JsonGenerator g, SerializationContext ctxt)
throws JacksonException
+ {
+ serializeContentsImpl(value0, g, ctxt,
+ false);
+ }
+
+ @Override
+ public void serializeFilteredContents(Object value, JsonGenerator g, SerializationContext provider)
+ throws JacksonException
+ {
+ serializeContentsImpl(value, g, provider,
+ provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private void serializeContentsImpl(Object value0, JsonGenerator g, SerializationContext ctxt, boolean filtered)
+ throws JacksonException
{
final List> value = (List>) value0;
if (_elementSerializer != null) {
- serializeContentsUsing(value, g, ctxt, _elementSerializer);
+ if (filtered) {
+ serializeFilteredContentsUsing(value, g, ctxt, _elementSerializer);
+ } else {
+ serializeContentsUsing(value, g, ctxt, _elementSerializer);
+ }
return;
}
if (_valueTypeSerializer != null) {
- serializeTypedContents(value, g, ctxt);
+ if (filtered) {
+ serializeFilteredTypedContents(value, g, ctxt);
+ } else {
+ serializeTypedContents(value, g, ctxt);
+ }
return;
}
final int len = value.size();
@@ -102,6 +156,9 @@ public void serializeContents(Object value0, JsonGenerator g, SerializationConte
for (; i < len; ++i) {
Object elem = value.get(i);
if (elem == null) {
+ if (filtered && _suppressNulls) {
+ continue;
+ }
ctxt.defaultSerializeNullValue(g);
} else {
Class> cc = elem.getClass();
@@ -115,6 +172,10 @@ public void serializeContents(Object value0, JsonGenerator g, SerializationConte
serializer = _findAndAddDynamic(ctxt, cc);
}
}
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(elem, serializer, ctxt)) {
+ continue;
+ }
serializer.serialize(elem, g, ctxt);
}
}
@@ -123,9 +184,25 @@ public void serializeContents(Object value0, JsonGenerator g, SerializationConte
}
}
- public void serializeContentsUsing(List> value, JsonGenerator jgen, SerializationContext provider,
+ public void serializeContentsUsing(List> value, JsonGenerator jgen, SerializationContext ctxt,
ValueSerializer ser)
throws JacksonException
+ {
+ serializeContentsUsingImpl(value, jgen, ctxt, ser,
+ false);
+ }
+
+ private void serializeFilteredContentsUsing(List> value, JsonGenerator jgen, SerializationContext ctxt,
+ ValueSerializer ser)
+ throws JacksonException
+ {
+ serializeContentsUsingImpl(value, jgen, ctxt, ser,
+ ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private void serializeContentsUsingImpl(List> value, JsonGenerator jgen, SerializationContext ctxt,
+ ValueSerializer ser, boolean filtered)
+ throws JacksonException
{
final int len = value.size();
if (len == 0) {
@@ -136,20 +213,43 @@ public void serializeContentsUsing(List> value, JsonGenerator jgen, Serializat
Object elem = value.get(i);
try {
if (elem == null) {
- provider.defaultSerializeNullValue(jgen);
- } else if (typeSer == null) {
- ser.serialize(elem, jgen, provider);
+ if (filtered && _suppressNulls) {
+ continue;
+ }
+ ctxt.defaultSerializeNullValue(jgen);
} else {
- ser.serializeWithType(elem, jgen, provider, typeSer);
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(elem, ser, ctxt)) {
+ continue;
+ }
+ if (typeSer == null) {
+ ser.serialize(elem, jgen, ctxt);
+ } else {
+ ser.serializeWithType(elem, jgen, ctxt, typeSer);
+ }
}
} catch (Exception e) {
// [JACKSON-55] Need to add reference information
- wrapAndThrow(provider, e, value, i);
+ wrapAndThrow(ctxt, e, value, i);
}
}
}
- public void serializeTypedContents(List> value, JsonGenerator g, SerializationContext ctxt)
+ public void serializeTypedContents(List> value, JsonGenerator jgen, SerializationContext ctxt)
+ throws JacksonException
+ {
+ serializeTypedContentsImpl(value, jgen, ctxt,
+ false);
+ }
+
+ public void serializeFilteredTypedContents(List> value, JsonGenerator jgen, SerializationContext ctxt)
+ throws JacksonException
+ {
+ serializeTypedContentsImpl(value, jgen, ctxt,
+ ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private void serializeTypedContentsImpl(List> value, JsonGenerator jgen, SerializationContext ctxt, boolean filtered)
throws JacksonException
{
final int len = value.size();
@@ -163,7 +263,10 @@ public void serializeTypedContents(List> value, JsonGenerator g, Serialization
for (; i < len; ++i) {
Object elem = value.get(i);
if (elem == null) {
- ctxt.defaultSerializeNullValue(g);
+ if (filtered && _suppressNulls) {
+ continue;
+ }
+ ctxt.defaultSerializeNullValue(jgen);
} else {
Class> cc = elem.getClass();
ValueSerializer serializer = serializers.serializerFor(cc);
@@ -176,7 +279,11 @@ public void serializeTypedContents(List> value, JsonGenerator g, Serialization
}
serializers = _dynamicValueSerializers;
}
- serializer.serializeWithType(elem, g, ctxt, typeSer);
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(elem, serializer, ctxt)) {
+ continue;
+ }
+ serializer.serializeWithType(elem, jgen, ctxt, typeSer);
}
}
} catch (Exception e) {
diff --git a/src/main/java/tools/jackson/databind/ser/jdk/IndexedStringListSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/IndexedStringListSerializer.java
index 41f7ce62ae..8abfbcdeb6 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/IndexedStringListSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/IndexedStringListSerializer.java
@@ -33,14 +33,29 @@ protected IndexedStringListSerializer() {
super(List.class);
}
+ @Deprecated // since 3.1.0
public IndexedStringListSerializer(IndexedStringListSerializer src,
Boolean unwrapSingle) {
- super(src, unwrapSingle);
+ this(src, unwrapSingle, src._suppressableValue, src._suppressNulls);
+ }
+
+ /**
+ * @since 3.1.0
+ */
+ public IndexedStringListSerializer(IndexedStringListSerializer src,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
+ super(src, unwrapSingle, suppressableValue, suppressNulls);
}
@Override
public ValueSerializer> _withResolved(BeanProperty prop, Boolean unwrapSingle) {
- return new IndexedStringListSerializer(this, unwrapSingle);
+ return new IndexedStringListSerializer(this, unwrapSingle, null, false);
+ }
+
+ @Override
+ public ValueSerializer> _withResolved(BeanProperty prop, Boolean unwrapSingle,
+ Object suppressableValue, boolean suppressNulls) {
+ return new IndexedStringListSerializer(this, unwrapSingle, suppressableValue, suppressNulls);
}
@Override protected JsonNode contentSchema() { return createSchemaNode("string", true); }
@@ -65,12 +80,24 @@ 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 (provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_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 (provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, provider, len);
+ } else {
+ serializeContents(value, g, provider, len);
+ }
g.writeEndArray();
}
@@ -88,14 +115,35 @@ public void serializeWithType(List value, JsonGenerator g, Serialization
private final void serializeContents(List value, JsonGenerator g,
SerializationContext provider, int len) throws JacksonException
+ {
+ serializeContentsImpl(value, g, provider, len,
+ false);
+ }
+
+ private final void serializeFilteredContents(List value, JsonGenerator g,
+ SerializationContext provider, int len) throws JacksonException
+ {
+ serializeContentsImpl(value, g, provider, len,
+ provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private final void serializeContentsImpl(List value, JsonGenerator g,
+ SerializationContext provider, int len, boolean filtered) throws JacksonException
{
int i = 0;
try {
for (; i < len; ++i) {
String str = value.get(i);
if (str == null) {
+ if (filtered && _suppressNulls) {
+ continue;
+ }
provider.defaultSerializeNullValue(g);
} else {
+ // 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/tools/jackson/databind/ser/jdk/IterableSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/IterableSerializer.java
index cafde9ee98..2696f7e77f 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/IterableSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/IterableSerializer.java
@@ -18,10 +18,20 @@ public IterableSerializer(JavaType elemType, boolean staticTyping,
super(Iterable.class, elemType, staticTyping, vts, null);
}
+ @Deprecated // since 3.1.0
public IterableSerializer(IterableSerializer src,
TypeSerializer vts, ValueSerializer> valueSerializer,
Boolean unwrapSingle, BeanProperty property) {
- super(src, vts, valueSerializer, unwrapSingle, property);
+ this(src, vts, valueSerializer, unwrapSingle, property, null, false);
+ }
+
+ /**
+ * @since 3.1.0
+ */
+ public IterableSerializer(IterableSerializer src,
+ TypeSerializer vts, ValueSerializer> valueSerializer,
+ Boolean unwrapSingle, BeanProperty property, Object suppressableValue, boolean suppressNulls) {
+ super(src, vts, valueSerializer, unwrapSingle, property, suppressableValue, suppressNulls);
}
@Override
@@ -36,6 +46,13 @@ public IterableSerializer withResolved(BeanProperty property,
return new IterableSerializer(this, vts, elementSerializer, unwrapSingle, property);
}
+ @Override
+ public IterableSerializer withResolved(BeanProperty property,
+ TypeSerializer vts, ValueSerializer> elementSerializer,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
+ return new IterableSerializer(this, vts, elementSerializer, unwrapSingle, property, suppressableValue, suppressNulls);
+ }
+
/*
/**********************************************************************
/* Accessors
@@ -113,4 +130,12 @@ public void serializeContents(Iterable> value, JsonGenerator g,
} while (it.hasNext());
}
}
+
+ @Override
+ protected void serializeFilteredContents(Iterable> value, JsonGenerator g, SerializationContext ctxt)
+ throws JacksonException
+ {
+ // TODO: Implement later?
+ serializeContents(value, g, ctxt);
+ }
}
diff --git a/src/main/java/tools/jackson/databind/ser/jdk/IteratorSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/IteratorSerializer.java
index 1062ceb765..29138caad1 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/IteratorSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/IteratorSerializer.java
@@ -18,10 +18,21 @@ public IteratorSerializer(JavaType elemType, boolean staticTyping, TypeSerialize
super(Iterator.class, elemType, staticTyping, vts, null);
}
+ @Deprecated // since 3.1.0
public IteratorSerializer(IteratorSerializer src,
TypeSerializer vts, ValueSerializer> valueSerializer,
Boolean unwrapSingle, BeanProperty property) {
- super(src, vts, valueSerializer, unwrapSingle, property);
+ this(src, vts, valueSerializer, unwrapSingle, property, null, false);
+ }
+
+ /**
+ * @since 3.1.0
+ */
+ public IteratorSerializer(IteratorSerializer src,
+ TypeSerializer vts, ValueSerializer> valueSerializer,
+ Boolean unwrapSingle, BeanProperty property,
+ Object suppressableValue, boolean suppressNulls) {
+ super(src, vts, valueSerializer, unwrapSingle, property, suppressableValue, suppressNulls);
}
@Override
@@ -33,7 +44,16 @@ protected StdContainerSerializer> _withValueTypeSerializer(TypeSerializer vts)
public IteratorSerializer withResolved(BeanProperty property,
TypeSerializer vts, ValueSerializer> elementSerializer,
Boolean unwrapSingle) {
- return new IteratorSerializer(this, vts, elementSerializer, unwrapSingle, property);
+ return new IteratorSerializer(this, vts, elementSerializer, unwrapSingle, property,
+ null, false);
+ }
+
+ @Override
+ public IteratorSerializer withResolved(BeanProperty property,
+ TypeSerializer vts, ValueSerializer> elementSerializer,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
+ return new IteratorSerializer(this, vts, elementSerializer, unwrapSingle, property,
+ suppressableValue, suppressNulls);
}
/*
@@ -70,32 +90,66 @@ public final void serialize(Iterator> value, JsonGenerator gen,
}
*/
gen.writeStartArray(value);
- serializeContents(value, gen, provider);
+ if (provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, gen, provider);
+ } else {
+ serializeContents(value, gen, provider);
+ }
gen.writeEndArray();
}
@Override
public void serializeContents(Iterator> value, JsonGenerator g,
- SerializationContext provider)
+ SerializationContext ctxt)
throws JacksonException
+ {
+ serializeContentsImpl(value, g, ctxt,
+ false);
+ }
+
+ @Override
+ protected void serializeFilteredContents(Iterator> value, JsonGenerator g,
+ SerializationContext ctxt) throws JacksonException
+ {
+ serializeContentsImpl(value, g, ctxt,
+ ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private void serializeContentsImpl(Iterator> value, JsonGenerator g,
+ SerializationContext ctxt, boolean filtered) throws JacksonException
{
if (!value.hasNext()) {
return;
}
ValueSerializer serializer = _elementSerializer;
if (serializer == null) {
- _serializeDynamicContents(value, g, provider);
+ if (filtered) {
+ _serializeFilteredDynamicContents(value, g, ctxt);
+ } else {
+ _serializeDynamicContents(value, g, ctxt);
+ }
return;
}
final TypeSerializer typeSer = _valueTypeSerializer;
do {
Object elem = value.next();
if (elem == null) {
- provider.defaultSerializeNullValue(g);
- } else if (typeSer == null) {
- serializer.serialize(elem, g, provider);
+ if (filtered && _suppressNulls) {
+ continue;
+ }
+ ctxt.defaultSerializeNullValue(g);
} else {
- serializer.serializeWithType(elem, g, provider, typeSer);
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(elem, serializer, ctxt)) {
+ continue;
+ }
+ if (typeSer == null) {
+ serializer.serialize(elem, g, ctxt);
+ } else {
+ serializer.serializeWithType(elem, g, ctxt, typeSer);
+ }
}
} while (value.hasNext());
}
@@ -103,11 +157,28 @@ public void serializeContents(Iterator> value, JsonGenerator g,
protected void _serializeDynamicContents(Iterator> value, JsonGenerator g,
SerializationContext ctxt)
throws JacksonException
+ {
+ _serializeDynamicContentsImpl(value, g, ctxt,
+ false);
+ }
+
+ protected void _serializeFilteredDynamicContents(Iterator> value, JsonGenerator g,
+ SerializationContext ctxt) throws JacksonException
+ {
+ _serializeDynamicContentsImpl(value, g, ctxt,
+ ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private void _serializeDynamicContentsImpl(Iterator> value, JsonGenerator g,
+ SerializationContext ctxt, boolean filtered) throws JacksonException
{
final TypeSerializer typeSer = _valueTypeSerializer;
do {
Object elem = value.next();
if (elem == null) {
+ if (filtered && _suppressNulls) {
+ continue;
+ }
ctxt.defaultSerializeNullValue(g);
continue;
}
@@ -121,6 +192,10 @@ protected void _serializeDynamicContents(Iterator> value, JsonGenerator g,
serializer = _findAndAddDynamic(ctxt, cc);
}
}
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(elem, serializer, ctxt)) {
+ continue;
+ }
if (typeSer == null) {
serializer.serialize(elem, g, ctxt);
} else {
diff --git a/src/main/java/tools/jackson/databind/ser/jdk/StaticListSerializerBase.java b/src/main/java/tools/jackson/databind/ser/jdk/StaticListSerializerBase.java
index e91b9fa0a9..8c5fb18fa5 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/StaticListSerializerBase.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/StaticListSerializerBase.java
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.*;
@@ -13,6 +14,8 @@
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.std.StdSerializer;
+import tools.jackson.databind.util.ArrayBuilders;
+import tools.jackson.databind.util.BeanUtil;
/**
* Intermediate base class for Lists, Collections and Arrays
@@ -21,6 +24,9 @@
public abstract class StaticListSerializerBase>
extends StdSerializer
{
+ // since 3.1.0
+ 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
@@ -28,20 +34,59 @@ public abstract class StaticListSerializerBase>
*/
protected final Boolean _unwrapSingle;
+ /**
+ * Value that indicates suppression mechanism to use for
+ * content values (elements of container), if any; null
+ * for no filtering.
+ * @since 3.1.0
+ */
+ protected final Object _suppressableValue;
+
+ /**
+ * Flag that indicates whether nulls should be suppressed.
+ * @since 3.1.0
+ */
+ protected final boolean _suppressNulls;
+
protected StaticListSerializerBase(Class> cls) {
super(cls);
_unwrapSingle = null;
+ _suppressableValue = null;
+ _suppressNulls = false;
}
+ @Deprecated // since 3.1.0
protected StaticListSerializerBase(StaticListSerializerBase> src,
Boolean unwrapSingle) {
+ this(src, unwrapSingle, src._suppressableValue, src._suppressNulls);
+ }
+
+ /**
+ * @since 3.1.0
+ */
+ protected StaticListSerializerBase(StaticListSerializerBase> src,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
super(src);
_unwrapSingle = unwrapSingle;
+ _suppressableValue = suppressableValue;
+ _suppressNulls = suppressNulls;
}
+ @Deprecated // since 3.1.0
public abstract ValueSerializer> _withResolved(BeanProperty prop,
Boolean unwrapSingle);
+ /**
+ * To support `@JsonInclude`.
+ * Default implementation fallback to {@link StaticListSerializerBase#_withResolved(BeanProperty, Boolean, Object, boolean)}
+ * @since 3.1.0
+ */
+ public ValueSerializer> _withResolved(BeanProperty prop,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls
+ ) {
+ return _withResolved(prop, unwrapSingle);
+ }
+
/*
/**********************************************************
/* Post-processing
@@ -73,12 +118,62 @@ public ValueSerializer> createContextual(SerializationContext 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
@@ -113,4 +208,34 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t
@Override
public abstract void serializeWithType(T value, JsonGenerator g,
SerializationContext provider, TypeSerializer typeSer) throws JacksonException;
+
+ /**
+ * 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 ctxt {@link SerializationContext}
+ * @return true if element should be serialized, false if suppressed
+ *
+ * @since 2.21
+ */
+ protected final boolean _shouldSerializeElement(Object elem, ValueSerializer serializer,
+ SerializationContext ctxt) throws JacksonException
+ {
+ if (_suppressableValue == null) {
+ return true;
+ }
+ if (_suppressableValue == MARKER_FOR_EMPTY) {
+ if (serializer != null) {
+ return !serializer.isEmpty(ctxt, 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/tools/jackson/databind/ser/jdk/StringCollectionSerializer.java b/src/main/java/tools/jackson/databind/ser/jdk/StringCollectionSerializer.java
index c3747272d0..5114136f4f 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/StringCollectionSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/StringCollectionSerializer.java
@@ -33,15 +33,30 @@ protected StringCollectionSerializer() {
super(Collection.class);
}
+ @Deprecated // since 3.1.0
protected StringCollectionSerializer(StringCollectionSerializer src,
Boolean unwrapSingle)
{
- super(src, unwrapSingle);
+ this(src, unwrapSingle, null, false);
+ }
+
+ /**
+ * @since 3.1.0p
+ */
+ protected StringCollectionSerializer(StringCollectionSerializer src,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls)
+ {
+ super(src, unwrapSingle, suppressableValue, suppressNulls);
}
@Override
public ValueSerializer> _withResolved(BeanProperty prop, Boolean unwrapSingle) {
- return new StringCollectionSerializer(this, unwrapSingle);
+ return new StringCollectionSerializer(this, unwrapSingle, null, false);
+ }
+
+ @Override
+ public ValueSerializer> _withResolved(BeanProperty prop, Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
+ return new StringCollectionSerializer(this, unwrapSingle, suppressableValue, suppressNulls);
}
@Override
@@ -69,12 +84,24 @@ 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 (provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, provider);
+ } else {
+ serializeContents(value, g, provider);
+ }
return;
}
}
g.writeStartArray(value, len);
- serializeContents(value, g, provider);
+ if (provider.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, provider);
+ } else {
+ serializeContents(value, g, provider);
+ }
g.writeEndArray();
}
@@ -91,22 +118,47 @@ public void serializeWithType(Collection value, JsonGenerator g,
}
private final void serializeContents(Collection value, JsonGenerator g,
- SerializationContext provider)
+ SerializationContext ctxt)
throws JacksonException
+ {
+ serializeContentsImpl(value, g, ctxt,
+ false);
+ }
+
+ private final void serializeFilteredContents(Collection value, JsonGenerator g,
+ SerializationContext ctxt)
+ throws JacksonException
+ {
+ serializeContentsImpl(value, g, ctxt,
+ ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private final void serializeContentsImpl(Collection value, JsonGenerator g,
+ SerializationContext ctxt, boolean filtered)
+ throws JacksonException
{
int i = 0;
try {
for (String str : value) {
if (str == null) {
- provider.defaultSerializeNullValue(g);
+ if (filtered && _suppressNulls) {
+ ++i;
+ continue;
+ }
+ ctxt.defaultSerializeNullValue(g);
} else {
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(str, null, ctxt)) {
+ ++i;
+ continue;
+ }
g.writeString(str);
}
++i;
}
} catch (Exception e) {
- wrapAndThrow(provider, e, value, i);
+ wrapAndThrow(ctxt, e, value, i);
}
}
}
diff --git a/src/main/java/tools/jackson/databind/ser/std/AsArraySerializerBase.java b/src/main/java/tools/jackson/databind/ser/std/AsArraySerializerBase.java
index ab2c6ec985..47bac61f1c 100644
--- a/src/main/java/tools/jackson/databind/ser/std/AsArraySerializerBase.java
+++ b/src/main/java/tools/jackson/databind/ser/std/AsArraySerializerBase.java
@@ -4,12 +4,15 @@
import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
import tools.jackson.core.*;
import tools.jackson.core.type.WritableTypeId;
import tools.jackson.databind.*;
import tools.jackson.databind.introspect.AnnotatedMember;
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import tools.jackson.databind.jsontype.TypeSerializer;
+import tools.jackson.databind.util.ArrayBuilders;
+import tools.jackson.databind.util.BeanUtil;
/**
* Base class for serializers that will output contents as JSON
@@ -23,6 +26,22 @@ public abstract class AsArraySerializerBase
protected final boolean _staticTyping;
+ public final static Object MARKER_FOR_EMPTY = JsonInclude.Include.NON_EMPTY;
+
+ /**
+ * Value that indicates suppression mechanism to use for
+ * content values (elements of container), if any; null
+ * for no filtering.
+ * @since 3.1.0
+ */
+ protected final Object _suppressableValue;
+
+ /**
+ * Flag that indicates whether nulls should be suppressed.
+ * @since 3.1.0
+ */
+ protected final boolean _suppressNulls;
+
/**
* Setting for specific local override for "unwrap single element arrays":
* true for enable unwrapping, false for preventing it, `null` for using
@@ -53,7 +72,7 @@ public abstract class AsArraySerializerBase
protected AsArraySerializerBase(Class> cls, JavaType elementType, boolean staticTyping,
TypeSerializer vts, ValueSerializer> elementSerializer)
{
- this(cls, elementType, staticTyping, vts, elementSerializer, null);
+ this(cls, elementType, staticTyping, vts, elementSerializer, null, null);
}
/**
@@ -71,15 +90,29 @@ protected AsArraySerializerBase(Class> cls, JavaType elementType, boolean stat
_valueTypeSerializer = vts;
_elementSerializer = (ValueSerializer) elementSerializer;
_unwrapSingle = unwrapSingle;
+ _suppressableValue = null;
+ _suppressNulls = false;
+ }
+
+ @Deprecated // since 3.1.0
+ @SuppressWarnings("unchecked")
+ protected AsArraySerializerBase(Class> cls, JavaType elementType, boolean staticTyping,
+ TypeSerializer vts, ValueSerializer> elementSerializer,
+ Boolean unwrapSingle, BeanProperty property)
+ {
+ this(cls, elementType, staticTyping, vts, elementSerializer, unwrapSingle, property, null, false);
}
/**
* General purpose constructor. Use contextual constructors, if possible.
+ *
+ * @since 3.1.0
*/
@SuppressWarnings("unchecked")
protected AsArraySerializerBase(Class> cls, JavaType elementType, boolean staticTyping,
TypeSerializer vts, ValueSerializer> elementSerializer,
- Boolean unwrapSingle, BeanProperty property)
+ Boolean unwrapSingle, BeanProperty property,
+ Object suppressableValue, boolean suppressNulls)
{
super(cls, property);
_elementType = elementType;
@@ -88,8 +121,11 @@ protected AsArraySerializerBase(Class> cls, JavaType elementType, boolean stat
_valueTypeSerializer = vts;
_elementSerializer = (ValueSerializer) elementSerializer;
_unwrapSingle = unwrapSingle;
+ _suppressableValue = suppressableValue;
+ _suppressNulls = suppressNulls;
}
+ @Deprecated // since 3.1.0
@SuppressWarnings("unchecked")
protected AsArraySerializerBase(AsArraySerializerBase> src,
TypeSerializer vts, ValueSerializer> elementSerializer,
@@ -101,12 +137,39 @@ protected AsArraySerializerBase(AsArraySerializerBase> src,
_valueTypeSerializer = vts;
_elementSerializer = (ValueSerializer) elementSerializer;
_unwrapSingle = unwrapSingle;
+ _suppressableValue = src._suppressableValue;
+ _suppressNulls = src._suppressNulls;
+ }
+
+ /**
+ * @since 3.1.0
+ */
+ @SuppressWarnings("unchecked")
+ protected AsArraySerializerBase(AsArraySerializerBase> src,
+ TypeSerializer vts, ValueSerializer> elementSerializer,
+ Boolean unwrapSingle, BeanProperty property,
+ Object suppressableValue, boolean suppressNulls)
+ {
+ super(src, property);
+ _elementType = src._elementType;
+ _staticTyping = src._staticTyping;
+ _valueTypeSerializer = vts;
+ _elementSerializer = (ValueSerializer) elementSerializer;
+ _unwrapSingle = unwrapSingle;
+ _suppressableValue = suppressableValue;
+ _suppressNulls = suppressNulls;
}
+ @Deprecated // since 3.1.0
protected abstract AsArraySerializerBase withResolved(BeanProperty property,
TypeSerializer vts, ValueSerializer> elementSerializer,
Boolean unwrapSingle);
+
+ protected abstract AsArraySerializerBase withResolved(BeanProperty property,
+ TypeSerializer vts, ValueSerializer> elementSerializer,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls);
+
/*
/**********************************************************************
/* Post-processing
@@ -157,11 +220,62 @@ public ValueSerializer> createContextual(SerializationContext ctxt,
}
}
}
+
+
+ // Handle content inclusion (similar to MapSerializer lines 560-609)
+ JsonInclude.Value inclV = findIncludeOverrides(ctxt, 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 = ctxt.includeFilterInstance(null, inclV.getContentFilter());
+ if (valueToSuppress == null) {
+ suppressNulls = true;
+ } else {
+ suppressNulls = ctxt.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;
}
@@ -224,6 +338,18 @@ public void serializeWithType(T value, JsonGenerator g, SerializationContext ctx
protected abstract void serializeContents(T value, JsonGenerator gen, SerializationContext provider)
throws JacksonException;
+ /**
+ * Support `@JsonInclude`
+ * Will fallback to {@link AsArraySerializerBase#serializeContents(Object, JsonGenerator, SerializationContext)} for backward compatibility.
+ *
+ * @since 3.1.0
+ */
+ protected void serializeFilteredContents(T value, JsonGenerator g, SerializationContext provider)
+ throws JacksonException
+ {
+ serializeContents(value, g, provider);
+ }
+
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JacksonException
@@ -238,4 +364,34 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t
}
visitArrayFormat(visitor, typeHint, valueSer, _elementType);
}
+
+ /**
+ * 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 3.1.0
+ */
+ protected final boolean _shouldSerializeElement(Object elem, ValueSerializer serializer,
+ SerializationContext provider) throws JacksonException
+ {
+ 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/test/java/tools/jackson/databind/ser/filter/JsonIncludeForCollection5369Test.java b/src/test/java/tools/jackson/databind/ser/filter/JsonIncludeForCollection5369Test.java
new file mode 100644
index 0000000000..12132a8722
--- /dev/null
+++ b/src/test/java/tools/jackson/databind/ser/filter/JsonIncludeForCollection5369Test.java
@@ -0,0 +1,232 @@
+package tools.jackson.databind.ser.filter;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import org.junit.jupiter.api.Test;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.SerializationFeature;
+import tools.jackson.databind.testutil.DatabindTestUtil;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+// [databind#5369] Support `@JsonInclude` for collection
+public class JsonIncludeForCollection5369Test
+ 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 = jsonMapperBuilder()
+ .enable(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ .build();
+
+ @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 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));
+ }
+
+
+}
\ No newline at end of file