Skip to content
8 changes: 8 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,14 @@ Yusuke Nojima (@ynojima)
from working on `@JsonCreator` parameter (regression in 3.2.0)
[3.2.1]

@seonwooj0810
* Fixed #6043: `FAIL_ON_UNKNOWN_PROPERTIES` has no effect with `JsonFormat.Shape.ARRAY`
when using creator-based instantiation (such as `record`)
[3.2.1]
* Fixed #6065: `SerializationFeature.APPLY_JSON_INCLUDE_FOR_CONTAINERS` does not fully
remove empty collection during serialization
[3.3.0]

Alex Crowell (@alex-crowell)
* Reported #1127: `@JsonTypeInfo` with `EXTERNAL_PROPERTY` does not handle
arrays of polymorphic types
Expand Down
4 changes: 4 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Versions: 3.x (for earlier see VERSION-2.x)

3.3.0 (not yet released)

#6065: `SerializationFeature.APPLY_JSON_INCLUDE_FOR_CONTAINERS` does not fully
remove empty collection during serialization
(reported by @doeiqts)
(fix by @seonwooj0810)
#1127: `@JsonTypeInfo` with `EXTERNAL_PROPERTY` does not handle arrays of
polymorphic types: now fails eagerly with clear `InvalidDefinitionException`
(on both serialization and deserialization) instead of confusing low-level error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,15 @@ protected CollectionSerializer withResolved(BeanProperty property,

@Override
public boolean isEmpty(SerializationContext prov, Collection<?> value) {
return value.isEmpty();
if (value.isEmpty()) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// a collection whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
return _allElementsSuppressed(prov, value.iterator());
}
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ public EnumSetSerializer withResolved(BeanProperty property,

@Override
public boolean isEmpty(SerializationContext prov, EnumSet<? extends Enum<?>> value) {
return value.isEmpty();
if (value.isEmpty()) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an EnumSet whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
return _allElementsSuppressed(prov, value.iterator());
}
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,16 @@ public IndexedListSerializer withResolved(BeanProperty property,

@Override
public boolean isEmpty(SerializationContext prov, Object value) {
return ((List<?>)value).isEmpty();
List<?> list = (List<?>) value;
if (list.isEmpty()) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// a list whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
return _allElementsSuppressed(prov, list.iterator());
}
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,16 @@ public IterableSerializer withResolved(BeanProperty property,

@Override
public boolean isEmpty(SerializationContext ctxt, Iterable<?> value) {
// Not really good way to implement this, but has to do for now:
return !value.iterator().hasNext();
Iterator<?> it = value.iterator();
if (!it.hasNext()) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an Iterable whose every element is suppressed is considered empty
if (_needToCheckFiltering(ctxt)) {
return _allElementsSuppressed(ctxt, it);
}
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,20 @@ public ValueSerializer<?> getContentSerializer() {

@Override
public boolean isEmpty(SerializationContext prov, boolean[] value) {
return value.length == 0;
if (value.length == 0) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an array whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
for (boolean v : value) {
if (_shouldSerializeElement(prov, Boolean.valueOf(v))) {
return false;
}
}
return true;
}
return false;
}

@Override
Expand Down Expand Up @@ -226,7 +239,20 @@ public ValueSerializer<?> getContentSerializer() {

@Override
public boolean isEmpty(SerializationContext prov, short[] value) {
return value.length == 0;
if (value.length == 0) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an array whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
for (short v : value) {
if (_shouldSerializeElement(prov, Short.valueOf(v))) {
return false;
}
}
return true;
}
return false;
}

@Override
Expand Down Expand Up @@ -378,7 +404,20 @@ public ValueSerializer<?> getContentSerializer() {

@Override
public boolean isEmpty(SerializationContext prov, int[] value) {
return value.length == 0;
if (value.length == 0) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an array whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
for (int v : value) {
if (_shouldSerializeElement(prov, Integer.valueOf(v))) {
return false;
}
}
return true;
}
return false;
}

@Override
Expand Down Expand Up @@ -460,7 +499,20 @@ public ValueSerializer<?> getContentSerializer() {

@Override
public boolean isEmpty(SerializationContext prov, long[] value) {
return value.length == 0;
if (value.length == 0) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an array whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
for (long v : value) {
if (_shouldSerializeElement(prov, Long.valueOf(v))) {
return false;
}
}
return true;
}
return false;
}

@Override
Expand Down Expand Up @@ -547,7 +599,20 @@ public ValueSerializer<?> getContentSerializer() {

@Override
public boolean isEmpty(SerializationContext prov, float[] value) {
return value.length == 0;
if (value.length == 0) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an array whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
for (float v : value) {
if (_shouldSerializeElement(prov, Float.valueOf(v))) {
return false;
}
}
return true;
}
return false;
}

@Override
Expand Down Expand Up @@ -649,7 +714,20 @@ public ValueSerializer<?> getContentSerializer() {

@Override
public boolean isEmpty(SerializationContext prov, double[] value) {
return value.length == 0;
if (value.length == 0) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an array whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
for (double v : value) {
if (_shouldSerializeElement(prov, Double.valueOf(v))) {
return false;
}
}
return true;
}
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,15 @@ public ValueSerializer<?> getContentSerializer() {

@Override
public boolean isEmpty(SerializationContext prov, Object[] value) {
return value.length == 0;
if (value.length == 0) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an array whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
return _allElementsSuppressed(prov, value);
}
return false;
}

@Override
Expand Down Expand Up @@ -480,6 +488,49 @@ protected boolean _shouldSerializeElement(SerializationContext ctxt,
return !_suppressableValue.equals(elem);
}

/**
* Helper method for content-aware emptiness checks (used by {@link #isEmpty}):
* when content {@code @JsonInclude} is applied to containers, an array is
* considered empty if every one of its elements would be suppressed during
* serialization (so the array would render as an empty array). Callers should
* only invoke this when {@link #_needToCheckFiltering} returns true.
*
* @since 3.3.0
*/
protected boolean _allElementsSuppressed(SerializationContext ctxt, Object[] value)
{
for (Object elem : value) {
if (elem == null) {
if (_suppressNulls) {
continue;
}
return false;
}
// Only the "empty" check needs an element serializer; other suppression
// mechanisms compare the element directly, so avoid resolving one
ValueSerializer<Object> serializer = null;
if (_suppressableValue == MARKER_FOR_EMPTY) {
serializer = _elementSerializer;
if (serializer == null) {
Class<?> cc = elem.getClass();
serializer = _dynamicValueSerializers.serializerFor(cc);
if (serializer == null) {
if (_elementType.hasGenericTypes()) {
serializer = _findAndAddDynamic(ctxt,
ctxt.constructSpecializedType(_elementType, cc));
} else {
serializer = _findAndAddDynamic(ctxt, cc);
}
}
}
}
if (_shouldSerializeElement(ctxt, elem, serializer)) {
return false;
}
}
return true;
}

@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,26 @@ public ValueSerializer<?> createContextual(SerializationContext ctxt,

@Override
public boolean isEmpty(SerializationContext provider, T value) {
return (value == null) || (value.isEmpty());
if ((value == null) || value.isEmpty()) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// a collection whose every element is suppressed is considered empty
if (_needToCheckFiltering(provider)) {
for (Object elem : value) {
if (elem == null) {
if (!_suppressNulls) {
return false;
}
// Elements are "natural" types (Strings), never have content serializer
// (see `createContextual()`), so pass `null`, same as `serializeContents()`
} else if (_shouldSerializeElement(elem, null, provider)) {
return false;
}
}
return true;
}
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,26 @@ public ValueSerializer<?> getContentSerializer() {

@Override
public boolean isEmpty(SerializationContext prov, String[] value) {
return (value.length == 0);
if (value.length == 0) {
return true;
}
// [databind#6065]: with content @JsonInclude applied to containers,
// an array whose every element is suppressed is considered empty
if (_needToCheckFiltering(prov)) {
for (String str : value) {
if (str == null) {
if (_suppressNulls) {
continue;
}
return false;
}
if (_shouldSerializeElement(prov, str, _elementSerializer)) {
return false;
}
}
return true;
}
return false;
}

@Override
Expand Down
Loading