From f8d34e2f5b8c1d6df604205a1436df41ccbf0460 Mon Sep 17 00:00:00 2001 From: Oleksii Khomchenko Date: Sun, 2 Feb 2020 14:51:03 -0800 Subject: [PATCH] Fix #2592: JsonInclude support for any getters Fix will not work if filter is used as it triggers separate execution path. --- .../jackson/databind/ser/AnyGetterWriter.java | 2 +- .../databind/ser/std/MapSerializer.java | 38 ++++---- .../jackson/databind/ser/AnyGetterTest.java | 90 +++++++++++++++++++ 3 files changed, 107 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java index 045adeaea7..c50bd0b85d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java @@ -59,7 +59,7 @@ public void getAndSerialize(Object bean, JsonGenerator gen, SerializerProvider p } // 23-Feb-2015, tatu: Nasty, but has to do (for now) if (_mapSerializer != null) { - _mapSerializer.serializeFields((Map) value, gen, provider); + _mapSerializer.serializeWithoutTypeInfo((Map) value, gen, provider); return; } _serializer.serialize(value, gen, provider); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java index 16a25ed616..1c1aaad94e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/MapSerializer.java @@ -628,21 +628,7 @@ public void serialize(Map value, JsonGenerator gen, SerializerProvider prov throws IOException { gen.writeStartObject(value); - if (!value.isEmpty()) { - if (_sortKeys || provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) { - value = _orderEntries(value, gen, provider); - } - PropertyFilter pf; - if ((_filterId != null) && (pf = findPropertyFilter(provider, _filterId, value)) != null) { - serializeFilteredFields(value, gen, provider, pf, _suppressableValue); - } else if ((_suppressableValue != null) || _suppressNulls) { - serializeOptionalFields(value, gen, provider, _suppressableValue); - } else if (_valueSerializer != null) { - serializeFieldsUsing(value, gen, provider, _valueSerializer); - } else { - serializeFields(value, gen, provider); - } - } + serializeWithoutTypeInfo(value, gen, provider); gen.writeEndObject(); } @@ -655,6 +641,21 @@ public void serializeWithType(Map value, JsonGenerator gen, SerializerProvi gen.setCurrentValue(value); WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.START_OBJECT)); + serializeWithoutTypeInfo(value, gen, provider); + typeSer.writeTypeSuffix(gen, typeIdDef); + } + + /* + /********************************************************** + /* Secondary serialization methods + /********************************************************** + */ + + /** + * General-purpose serialization for contents without writing object type. Will suppress, filter and + * use custom serializers. + */ + public void serializeWithoutTypeInfo(Map value, JsonGenerator gen, SerializerProvider provider) throws IOException { if (!value.isEmpty()) { if (_sortKeys || provider.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) { value = _orderEntries(value, gen, provider); @@ -670,15 +671,8 @@ public void serializeWithType(Map value, JsonGenerator gen, SerializerProvi serializeFields(value, gen, provider); } } - typeSer.writeTypeSuffix(gen, typeIdDef); } - /* - /********************************************************** - /* Secondary serialization methods - /********************************************************** - */ - /** * General-purpose serialization for contents, where we do not necessarily know * the value serialization, but diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java index 23746d492a..3cf714c4e7 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.ser.impl.*; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer; @@ -131,6 +132,37 @@ public void serialize(String value, JsonGenerator gen, } } + static class Bean2592NoAnnotations + { + protected Map properties = new HashMap<>(); + + @JsonAnyGetter + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public void add(String key, String value) { + properties.put(key, value); + } + } + + static class Bean2592PropertyIncludeNonEmpty extends Bean2592NoAnnotations + { + @JsonInclude(content = JsonInclude.Include.NON_EMPTY) + @JsonAnyGetter + @Override + public Map getProperties() { + return properties; + } + } + + @JsonFilter("Bean2592") + static class Bean2592WithFilter extends Bean2592NoAnnotations {} + /* /********************************************************** /* Test methods @@ -196,4 +228,62 @@ public void testAnyGetterWithValueSerializer() throws Exception String json = mapper.writeValueAsString(input); assertEquals("{\"key\":\"VALUE\"}", json); } + + // [databind#2592] + public void testAnyGetterWithMapperDefaultIncludeNonEmpty() throws Exception + { + ObjectMapper mapper = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + Bean2592NoAnnotations input = new Bean2592NoAnnotations(); + input.add("non-empty", "property"); + input.add("empty", ""); + input.add("null", null); + String json = mapper.writeValueAsString(input); + assertEquals("{\"non-empty\":\"property\"}", json); + } + + // [databind#2592] + public void testAnyGetterWithMapperDefaultIncludeNonEmptyAndFilterOnBean() throws Exception + { + FilterProvider filters = new SimpleFilterProvider() + .addFilter("Bean2592", SimpleBeanPropertyFilter.serializeAllExcept("something")); + ObjectMapper mapper = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + .setFilterProvider(filters); + Bean2592WithFilter input = new Bean2592WithFilter(); + input.add("non-empty", "property"); + input.add("empty", ""); + input.add("null", null); + String json = mapper.writeValueAsString(input); + // Unfortunately path for bean with filter is different. It still skips nulls. + assertEquals("{\"non-empty\":\"property\",\"empty\":\"\"}", json); + } + + // [databind#2592] + public void testAnyGetterWithPropertyIncludeNonEmpty() throws Exception + { + ObjectMapper mapper = new ObjectMapper(); + Bean2592PropertyIncludeNonEmpty input = new Bean2592PropertyIncludeNonEmpty(); + input.add("non-empty", "property"); + input.add("empty", ""); + input.add("null", null); + String json = mapper.writeValueAsString(input); + assertEquals("{\"non-empty\":\"property\"}", json); + } + + // [databind#2592] + public void testAnyGetterConfigIncludeNonEmpty() throws Exception + { + ObjectMapper mapper = new ObjectMapper(); + mapper.configOverride(Map.class).setInclude(JsonInclude.Value.construct( + JsonInclude.Include.USE_DEFAULTS, + JsonInclude.Include.NON_EMPTY + )); + Bean2592NoAnnotations input = new Bean2592NoAnnotations(); + input.add("non-empty", "property"); + input.add("empty", ""); + input.add("null", null); + String json = mapper.writeValueAsString(input); + assertEquals("{\"non-empty\":\"property\"}", json); + } }