diff --git a/src/main/java/tools/jackson/databind/SerializationFeature.java b/src/main/java/tools/jackson/databind/SerializationFeature.java
index e778426c9b8..e5c30c0f4a6 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 a3d02c88714..42c41bd34be 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/CollectionSerializer.java
@@ -47,23 +47,44 @@ public CollectionSerializer(JavaType elemType, boolean staticTyping, TypeSeriali
_maybeEnumSet = elemType.isEnumType() || elemType.isJavaLangObject();
}
+ @Deprecated // since 3.1
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
+ */
+ 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;
}
@Override
protected StdContainerSerializer> _withValueTypeSerializer(TypeSerializer vts) {
- return new CollectionSerializer(this, vts, _elementSerializer, _unwrapSingle, _property);
+ return new CollectionSerializer(this, vts, _elementSerializer, _unwrapSingle, _property,
+ _suppressableValue, _suppressNulls);
}
+ @Deprecated // @since 3.1
@Override
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
+ @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);
}
/*
@@ -98,22 +119,51 @@ public final void serialize(Collection> value, JsonGenerator g,
if (((_unwrapSingle == null) &&
ctxt.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, ctxt);
+ if (ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, ctxt);
+ } else {
+ serializeContents(value, g, ctxt);
+ }
return;
}
}
g.writeStartArray(value, len);
- serializeContents(value, g, ctxt);
+ if (ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, ctxt);
+ } else {
+ serializeContents(value, g, ctxt);
+ }
g.writeEndArray();
}
@Override
- public void serializeContents(Collection> value, JsonGenerator g,
+ public void serializeContents(Collection> value, JsonGenerator g, SerializationContext ctxt)
+ {
+ 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();
@@ -130,6 +180,10 @@ public void serializeContents(Collection> value, JsonGenerator g,
do {
Object elem = it.next();
if (elem == null) {
+ if (filtered && _suppressNulls) {
+ ++i;
+ continue;
+ }
ctxt.defaultSerializeNullValue(g);
} else {
Class> cc = elem.getClass();
@@ -142,6 +196,11 @@ public void serializeContents(Collection> value, JsonGenerator g,
}
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 {
@@ -158,6 +217,20 @@ public void serializeContents(Collection> value, JsonGenerator g,
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()) {
@@ -169,8 +242,17 @@ public void serializeContentsUsing(Collection> value, JsonGenerator g,
Object elem = it.next();
try {
if (elem == null) {
+ 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, ctxt);
} else {
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 1d7f3425180..9dc34c3a023 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
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
+ */
+ 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
@@ -26,11 +37,19 @@ protected EnumSetSerializer _withValueTypeSerializer(TypeSerializer vts) {
return this;
}
+ @Deprecated // @since 3.1
@Override
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 +99,5 @@ public void serializeContents(EnumSet extends Enum>> value, JsonGenerator g,
enumSer.serialize(en, g, 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 0902165f987..213eda74470 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/IndexedListSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/IndexedListSerializer.java
@@ -26,18 +26,31 @@ public IndexedListSerializer(JavaType elemType, boolean staticTyping, TypeSerial
super(List.class, elemType, staticTyping, vts, valueSerializer);
}
+ @Deprecated // since 3.1
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
+ */
+ 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
protected StdContainerSerializer> _withValueTypeSerializer(TypeSerializer vts) {
return new IndexedListSerializer(this,
- vts, _elementSerializer, _unwrapSingle, _property);
+ vts, _elementSerializer, _unwrapSingle, _property,
+ _suppressableValue, _suppressNulls);
+
}
+ @Deprecated // @since 3.1
@Override
public IndexedListSerializer withResolved(BeanProperty property,
TypeSerializer vts, ValueSerializer> elementSerializer,
@@ -45,6 +58,14 @@ 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
@@ -72,26 +93,61 @@ public final void serialize(Object value0, JsonGenerator g,
if (((_unwrapSingle == null) &&
ctxt.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, ctxt);
+ if (ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, ctxt);
+ } else {
+ serializeContents(value, g, ctxt);
+ }
return;
}
}
g.writeStartArray(value, len);
- serializeContents(value, g, ctxt);
+ if (ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, ctxt);
+ } else {
+ serializeContents(value, g, ctxt);
+ }
g.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();
@@ -103,6 +159,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();
@@ -116,6 +175,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);
}
}
@@ -127,6 +190,21 @@ public void serializeContents(Object value0, JsonGenerator g, SerializationConte
public void serializeContentsUsing(List> value, JsonGenerator g,
SerializationContext ctxt, ValueSerializer ser)
throws JacksonException
+ {
+ serializeContentsUsingImpl(value, g, ctxt, ser, false);
+ }
+
+ private void serializeFilteredContentsUsing(List> 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(List> value, JsonGenerator g,
+ SerializationContext ctxt, ValueSerializer ser, boolean filtered)
+ throws JacksonException
{
final int len = value.size();
if (len == 0) {
@@ -137,11 +215,20 @@ public void serializeContentsUsing(List> value, JsonGenerator g,
Object elem = value.get(i);
try {
if (elem == null) {
+ if (filtered && _suppressNulls) {
+ continue;
+ }
ctxt.defaultSerializeNullValue(g);
- } else if (typeSer == null) {
- ser.serialize(elem, g, ctxt);
} else {
- ser.serializeWithType(elem, g, ctxt, 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, g, ctxt);
+ } else {
+ ser.serializeWithType(elem, g, ctxt, typeSer);
+ }
}
} catch (Exception e) {
// [JACKSON-55] Need to add reference information
@@ -150,7 +237,23 @@ public void serializeContentsUsing(List> value, JsonGenerator g,
}
}
- public void serializeTypedContents(List> value, JsonGenerator g, SerializationContext ctxt)
+ public void serializeTypedContents(List> value, JsonGenerator g,
+ SerializationContext ctxt)
+ throws JacksonException
+ {
+ serializeTypedContentsImpl(value, g, ctxt, false);
+ }
+
+ public void serializeFilteredTypedContents(List> value, JsonGenerator g,
+ SerializationContext ctxt)
+ throws JacksonException
+ {
+ serializeTypedContentsImpl(value, g, ctxt,
+ ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS));
+ }
+
+ private void serializeTypedContentsImpl(List> value, JsonGenerator g,
+ SerializationContext ctxt, boolean filtered)
throws JacksonException
{
final int len = value.size();
@@ -164,6 +267,9 @@ public void serializeTypedContents(List> value, JsonGenerator g, Serialization
for (; i < len; ++i) {
Object elem = value.get(i);
if (elem == null) {
+ if (filtered && _suppressNulls) {
+ continue;
+ }
ctxt.defaultSerializeNullValue(g);
} else {
Class> cc = elem.getClass();
@@ -177,6 +283,10 @@ public void serializeTypedContents(List> value, JsonGenerator g, Serialization
}
serializers = _dynamicValueSerializers;
}
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(elem, serializer, ctxt)) {
+ continue;
+ }
serializer.serializeWithType(elem, g, ctxt, typeSer);
}
}
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 47fc5f4ff0d..54a049d10c3 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,30 @@ protected IndexedStringListSerializer() {
super(List.class);
}
+ @Deprecated // since 3.1
public IndexedStringListSerializer(IndexedStringListSerializer src,
Boolean unwrapSingle) {
- super(src, unwrapSingle);
+ this(src, unwrapSingle, src._suppressableValue, src._suppressNulls);
}
+ /**
+ * @since 3.1
+ */
+ public IndexedStringListSerializer(IndexedStringListSerializer src,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
+ super(src, unwrapSingle, suppressableValue, suppressNulls);
+ }
+
+ @Deprecated // @since 3.1
@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); }
@@ -66,12 +82,24 @@ public void serialize(List value, JsonGenerator g,
if (((_unwrapSingle == null) &&
ctxt.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, ctxt, 1);
+ if (ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, ctxt, 1);
+ } else {
+ serializeContents(value, g, ctxt, 1);
+ }
return;
}
}
g.writeStartArray(value, len);
- serializeContents(value, g, ctxt, len);
+ if (ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, ctxt, len);
+ } else {
+ serializeContents(value, g, ctxt, len);
+ }
g.writeEndArray();
}
@@ -90,14 +118,36 @@ public void serializeWithType(List value, JsonGenerator g,
private final void serializeContents(List value, JsonGenerator g,
SerializationContext ctxt, int len)
throws JacksonException
+ {
+ serializeContentsImpl(value, g, ctxt, 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 ctxt, 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;
+ }
ctxt.defaultSerializeNullValue(g);
} else {
+ // Check if this element should be suppressed (only in filtered mode)
+ if (filtered && !_shouldSerializeElement(str, null, ctxt)) {
+ 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 81361df1b71..fa2b91928ed 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/IterableSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/IterableSerializer.java
@@ -18,17 +18,29 @@ public IterableSerializer(JavaType elemType, boolean staticTyping,
super(Iterable.class, elemType, staticTyping, vts, null);
}
+ @Deprecated // since 3.1
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
+ */
+ 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
protected StdContainerSerializer> _withValueTypeSerializer(TypeSerializer vts) {
- return new IterableSerializer(this, vts, _elementSerializer, _unwrapSingle, _property);
+ return new IterableSerializer(this, vts, _elementSerializer, _unwrapSingle, _property,
+ _suppressableValue, _suppressNulls);
}
+ @Deprecated // @since 3.1
@Override
public IterableSerializer withResolved(BeanProperty property,
TypeSerializer vts, ValueSerializer> elementSerializer,
@@ -36,6 +48,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
@@ -115,4 +134,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 19b79e199c9..b6ed2ef0215 100644
--- a/src/main/java/tools/jackson/databind/ser/jdk/IteratorSerializer.java
+++ b/src/main/java/tools/jackson/databind/ser/jdk/IteratorSerializer.java
@@ -18,22 +18,44 @@ public IteratorSerializer(JavaType elemType, boolean staticTyping, TypeSerialize
super(Iterator.class, elemType, staticTyping, vts, null);
}
+ @Deprecated // since 3.1
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
+ */
+ 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
protected StdContainerSerializer> _withValueTypeSerializer(TypeSerializer vts) {
- return new IteratorSerializer(this, vts, _elementSerializer, _unwrapSingle, _property);
+ return new IteratorSerializer(this, vts, _elementSerializer, _unwrapSingle, _property,
+ _suppressableValue, _suppressNulls);
}
+ @Deprecated // @since 3.1
@Override
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,7 +92,13 @@ public final void serialize(Iterator> value, JsonGenerator g,
}
*/
g.writeStartArray(value);
- serializeContents(value, g, ctxt);
+ if (ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, ctxt);
+ } else {
+ serializeContents(value, g, ctxt);
+ }
g.writeEndArray();
}
@@ -78,24 +106,51 @@ public final void serialize(Iterator> value, JsonGenerator g,
public void serializeContents(Iterator> value, JsonGenerator g,
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, ctxt);
+ if (filtered) {
+ _serializeFilteredDynamicContents(value, g, ctxt);
+ } else {
+ _serializeDynamicContents(value, g, ctxt);
+ }
return;
}
final TypeSerializer typeSer = _valueTypeSerializer;
do {
Object elem = value.next();
if (elem == null) {
+ if (filtered && _suppressNulls) {
+ continue;
+ }
ctxt.defaultSerializeNullValue(g);
- } else if (typeSer == null) {
- serializer.serialize(elem, g, ctxt);
} else {
- serializer.serializeWithType(elem, g, ctxt, 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 +158,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 +193,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 528bdb2e025..cd8aebd63d4 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
+ 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,63 @@ 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
+ */
+ protected final Object _suppressableValue;
+
+ /**
+ * Flag that indicates whether nulls should be suppressed.
+ *
+ * @since 3.1
+ */
+ protected final boolean _suppressNulls;
+
protected StaticListSerializerBase(Class> cls) {
super(cls);
_unwrapSingle = null;
+ _suppressableValue = null;
+ _suppressNulls = false;
}
+ @Deprecated // since 3.1
protected StaticListSerializerBase(StaticListSerializerBase> src,
Boolean unwrapSingle) {
+ this(src, unwrapSingle, src._suppressableValue, src._suppressNulls);
+ }
+
+ /**
+ * @since 3.1
+ */
+ protected StaticListSerializerBase(StaticListSerializerBase> src,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls) {
super(src);
_unwrapSingle = unwrapSingle;
+ _suppressableValue = suppressableValue;
+ _suppressNulls = suppressNulls;
}
+ // Being used by the new _withResolved(BeanProperty, Boolean, Object, boolean) method as fallback
+ @SuppressWarnings("DeprecatedIsStillUsed")
+ @Deprecated // since 3.1
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
+ */
+ public ValueSerializer> _withResolved(BeanProperty prop,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls
+ ) {
+ return _withResolved(prop, unwrapSingle);
+ }
+
/*
/**********************************************************
/* Post-processing
@@ -73,12 +122,62 @@ public ValueSerializer> createContextual(SerializationContext ctxt,
if (ser == null) {
ser = ctxt.findContentValueSerializer(String.class, property);
}
+ // Handle content inclusion (similar to MapSerializer lines 560-609)
+ JsonInclude.Value inclV = findIncludeOverrides(ctxt, 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(ctxt.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 = 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;
+ }
+ }
+ }
+
// 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
@@ -112,6 +211,35 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t
// just to make sure it gets implemented:
@Override
public abstract void serializeWithType(T value, JsonGenerator g,
- SerializationContext ctxt, TypeSerializer typeSer)
- throws JacksonException;
+ SerializationContext ctxt, 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 c18bdf21bd0..238a3c89b17 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,31 @@ protected StringCollectionSerializer() {
super(Collection.class);
}
+ @Deprecated // since 3.1
protected StringCollectionSerializer(StringCollectionSerializer src,
Boolean unwrapSingle)
{
- super(src, unwrapSingle);
+ this(src, unwrapSingle, null, false);
}
+ /**
+ * @since 3.1
+ */
+ protected StringCollectionSerializer(StringCollectionSerializer src,
+ Boolean unwrapSingle, Object suppressableValue, boolean suppressNulls)
+ {
+ super(src, unwrapSingle, suppressableValue, suppressNulls);
+ }
+
+ @Deprecated // @since 3.1
@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 +85,24 @@ public void serialize(Collection value, JsonGenerator g,
if (((_unwrapSingle == null) &&
ctxt.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED))
|| (_unwrapSingle == Boolean.TRUE)) {
- serializeContents(value, g, ctxt);
+ if (ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, ctxt);
+ } else {
+ serializeContents(value, g, ctxt);
+ }
return;
}
}
g.writeStartArray(value, len);
- serializeContents(value, g, ctxt);
+ if (ctxt.isEnabled(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ && ((_suppressableValue != null) || _suppressNulls)
+ ) {
+ serializeFilteredContents(value, g, ctxt);
+ } else {
+ serializeContents(value, g, ctxt);
+ }
g.writeEndArray();
}
@@ -93,14 +121,39 @@ public void serializeWithType(Collection value, JsonGenerator g,
private final void serializeContents(Collection value, JsonGenerator g,
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) {
+ 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;
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 333af2c588f..7334b2e085b 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,24 @@ 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
+ */
+ protected final Object _suppressableValue;
+
+ /**
+ * Flag that indicates whether nulls should be suppressed.
+ *
+ * @since 3.1
+ */
+ 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 +74,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 +92,28 @@ protected AsArraySerializerBase(Class> cls, JavaType elementType, boolean stat
_valueTypeSerializer = vts;
_elementSerializer = (ValueSerializer) elementSerializer;
_unwrapSingle = unwrapSingle;
+ _suppressableValue = null;
+ _suppressNulls = false;
+ }
+
+ @Deprecated // since 3.1
+ 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
*/
@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 +122,11 @@ protected AsArraySerializerBase(Class> cls, JavaType elementType, boolean stat
_valueTypeSerializer = vts;
_elementSerializer = (ValueSerializer) elementSerializer;
_unwrapSingle = unwrapSingle;
+ _suppressableValue = suppressableValue;
+ _suppressNulls = suppressNulls;
}
+ @Deprecated // since 3.1
@SuppressWarnings("unchecked")
protected AsArraySerializerBase(AsArraySerializerBase> src,
TypeSerializer vts, ValueSerializer> elementSerializer,
@@ -101,12 +138,39 @@ protected AsArraySerializerBase(AsArraySerializerBase> src,
_valueTypeSerializer = vts;
_elementSerializer = (ValueSerializer) elementSerializer;
_unwrapSingle = unwrapSingle;
+ _suppressableValue = src._suppressableValue;
+ _suppressNulls = src._suppressNulls;
}
+ /**
+ * @since 3.1
+ */
+ @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
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 +221,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;
}
@@ -225,6 +340,18 @@ protected abstract void serializeContents(T value, JsonGenerator g,
SerializationContext ctxt)
throws JacksonException;
+ /**
+ * Support `@JsonInclude`
+ * Will fallback to {@link AsArraySerializerBase#serializeContents(Object, JsonGenerator, SerializationContext)} for backward compatibility.
+ *
+ * @since 3.1
+ */
+ 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
@@ -239,4 +366,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
+ */
+ 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 00000000000..56635754ef4
--- /dev/null
+++ b/src/test/java/tools/jackson/databind/ser/filter/JsonIncludeForCollection5369Test.java
@@ -0,0 +1,387 @@
+package tools.jackson.databind.ser.filter;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.SerializationFeature;
+import tools.jackson.databind.testutil.DatabindTestUtil;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+// [databind#5369] Support `@JsonInclude` for collection
+public class JsonIncludeForCollection5369Test
+ extends DatabindTestUtil
+{
+ /*
+ /**********************************************************
+ /* POJOs + Filters (kept together)
+ /**********************************************************
+ */
+
+ // String "foo" filter
+ static class FooFilter {
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+ return "foo".equals(other);
+ }
+ }
+
+ static class FooListBean {
+ @JsonInclude(content = JsonInclude.Include.CUSTOM,
+ contentFilter = FooFilter.class)
+ public List items = new ArrayList<>();
+
+ FooListBean add(String value) {
+ items.add(value);
+ return this;
+ }
+ }
+
+ static class FooSetBean {
+ @JsonInclude(content = JsonInclude.Include.CUSTOM,
+ contentFilter = FooFilter.class)
+ public Set items = new LinkedHashSet<>();
+
+ FooSetBean add(String value) {
+ items.add(value);
+ return this;
+ }
+ }
+
+ // NON_NULL
+ static class NonNullListBean {
+ @JsonInclude(content = JsonInclude.Include.NON_NULL)
+ public List items = new ArrayList<>();
+
+ NonNullListBean add(String value) {
+ items.add(value);
+ return this;
+ }
+ }
+
+ // NON_EMPTY
+ static class NonEmptyListBean {
+ @JsonInclude(content = JsonInclude.Include.NON_EMPTY)
+ public List items = new ArrayList<>();
+
+ NonEmptyListBean add(String value) {
+ items.add(value);
+ return this;
+ }
+ }
+
+ // NON_DEFAULT
+ static class NonDefaultListBean {
+ @JsonInclude(content = JsonInclude.Include.NON_DEFAULT)
+ public List items = new ArrayList<>();
+
+ NonDefaultListBean add(String value) {
+ items.add(value);
+ return this;
+ }
+ }
+
+ // Integer = 42 filter
+ static class IntegerFilter {
+ @Override
+ public boolean equals(Object other) {
+ return Integer.valueOf(42).equals(other);
+ }
+ }
+
+ static class IntegerListPojo {
+ @JsonInclude(content = JsonInclude.Include.CUSTOM,
+ contentFilter = IntegerFilter.class)
+ public List values = new ArrayList<>();
+
+ IntegerListPojo add(int v) {
+ values.add(v);
+ return this;
+ }
+ }
+
+ // Short = 7 filter
+ static class ShortFilter {
+ @Override
+ public boolean equals(Object other) {
+ return Short.valueOf((short) 7).equals(other);
+ }
+ }
+
+ static class ShortListPojo {
+ @JsonInclude(content = JsonInclude.Include.CUSTOM,
+ contentFilter = ShortFilter.class)
+ public List values = new ArrayList<>();
+
+ ShortListPojo add(short v) {
+ values.add(v);
+ return this;
+ }
+ }
+
+ // Byte = 9 filter
+ static class ByteFilter {
+ @Override
+ public boolean equals(Object other) {
+ return Byte.valueOf((byte) 9).equals(other);
+ }
+ }
+
+ static class ByteListPojo {
+ @JsonInclude(content = JsonInclude.Include.CUSTOM,
+ contentFilter = ByteFilter.class)
+ public List values = new ArrayList<>();
+
+ ByteListPojo add(byte v) {
+ values.add(v);
+ return this;
+ }
+ }
+
+ // Long = 100 filter
+ static class LongFilter {
+ @Override
+ public boolean equals(Object other) {
+ return Long.valueOf(100L).equals(other);
+ }
+ }
+
+ static class LongListPojo {
+ @JsonInclude(content = JsonInclude.Include.CUSTOM,
+ contentFilter = LongFilter.class)
+ public List values = new ArrayList<>();
+
+ LongListPojo add(long v) {
+ values.add(v);
+ return this;
+ }
+ }
+
+ // Double = 1.25 filter
+ static class DoubleFilter {
+ @Override
+ public boolean equals(Object other) {
+ return Double.valueOf(1.25).equals(other);
+ }
+ }
+
+ static class DoubleListPojo {
+ @JsonInclude(content = JsonInclude.Include.CUSTOM,
+ contentFilter = DoubleFilter.class)
+ public List values = new ArrayList<>();
+
+ DoubleListPojo add(double v) {
+ values.add(v);
+ return this;
+ }
+ }
+
+ // Counting filter
+ static class CountingFooFilter {
+ static final AtomicInteger counter = new AtomicInteger();
+
+ @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<>();
+
+ CountingFooListBean add(String value) {
+ items.add(value);
+ return this;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ /*
+ /**********************************************************
+ /* Mapper
+ /**********************************************************
+ */
+
+ private final ObjectMapper MAPPER = jsonMapperBuilder()
+ .enable(SerializationFeature.APPLY_JSON_INCLUDE_FOR_COLLECTIONS)
+ .build();
+
+ /*
+ /**********************************************************
+ /* Tests
+ /**********************************************************
+ */
+
+ @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).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 testCustomFilterWithIntegerList() throws Exception {
+ IntegerListPojo input = new IntegerListPojo()
+ .add(1).add(42).add(2);
+
+ assertEquals(a2q("{'values':[1,2]}"),
+ MAPPER.writeValueAsString(input));
+ }
+
+ @Test
+ public void testCustomFilterWithShortList() throws Exception {
+ ShortListPojo input = new ShortListPojo()
+ .add((short) 1).add((short) 7).add((short) 2);
+
+ assertEquals(a2q("{'values':[1,2]}"),
+ MAPPER.writeValueAsString(input));
+ }
+
+ @Test
+ public void testCustomFilterWithByteList() throws Exception {
+ ByteListPojo input = new ByteListPojo()
+ .add((byte) 1).add((byte) 9).add((byte) 2);
+
+ assertEquals(a2q("{'values':[1,2]}"),
+ MAPPER.writeValueAsString(input));
+ }
+
+ @Test
+ public void testCustomFilterWithDoubleList() throws Exception {
+ DoubleListPojo input = new DoubleListPojo()
+ .add(0.5).add(1.25).add(2.5);
+
+ assertEquals(a2q("{'values':[0.5,2.5]}"),
+ MAPPER.writeValueAsString(input));
+ }
+
+ @Test
+ public void testCustomFilterWithLongList() throws Exception {
+ LongListPojo input = new LongListPojo()
+ .add(10L).add(100L).add(20L);
+
+ assertEquals(a2q("{'values':[10,20]}"),
+ 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)
+ );
+ }
+
+}