Skip to content

Commit 170c6d4

Browse files
committed
Fix #2909
1 parent 532641f commit 170c6d4

File tree

4 files changed

+136
-23
lines changed

4 files changed

+136
-23
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Project: jackson-databind
2424
values could be converted to integers losslessly
2525
(requested by Oguzhan U; implementation contributed by Siavash S)
2626
#2903: Allow preventing "Enum from integer" coercion using new `CoercionConfig` system
27+
#2909: `@JsonValue` not considered when evaluating inclusion
28+
(reported by chrylis@github)
2729

2830
2.12.0-rc1 (12-Oct-2020)
2931

src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java

Lines changed: 116 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import java.util.Set;
88

99
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
10+
1011
import com.fasterxml.jackson.core.*;
1112
import com.fasterxml.jackson.core.type.WritableTypeId;
13+
1214
import com.fasterxml.jackson.databind.*;
1315
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
1416
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
@@ -20,6 +22,7 @@
2022
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
2123
import com.fasterxml.jackson.databind.ser.BeanSerializer;
2224
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
25+
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
2326
import com.fasterxml.jackson.databind.util.ClassUtil;
2427

2528
/**
@@ -49,14 +52,29 @@ public class JsonValueSerializer
4952

5053
protected final BeanProperty _property;
5154

55+
/**
56+
* Declared type of the value accessed, as declared by accessor.
57+
*
58+
* @since 2.12
59+
*/
60+
protected final JavaType _valueType;
61+
5262
/**
5363
* This is a flag that is set in rare (?) cases where this serializer
5464
* is used for "natural" types (boolean, int, String, double); and where
5565
* we actually must force type information wrapping, even though
5666
* one would not normally be added.
5767
*/
5868
protected final boolean _forceTypeInformation;
59-
69+
70+
/**
71+
* If value type cannot be statically determined, mapping from
72+
* runtime value types to serializers are cached in this object.
73+
*
74+
* @since 2.12
75+
*/
76+
protected transient PropertySerializerMap _dynamicSerializers;
77+
6078
/*
6179
/**********************************************************
6280
/* Life-cycle
@@ -77,9 +95,11 @@ public JsonValueSerializer(AnnotatedMember accessor, JsonSerializer<?> ser)
7795
{
7896
super(accessor.getType());
7997
_accessor = accessor;
98+
_valueType = accessor.getType();
8099
_valueSerializer = (JsonSerializer<Object>) ser;
81100
_property = null;
82101
_forceTypeInformation = true; // gets reconsidered when we are contextualized
102+
_dynamicSerializers = PropertySerializerMap.emptyForProperties();
83103
}
84104

85105
@SuppressWarnings("unchecked")
@@ -88,9 +108,11 @@ public JsonValueSerializer(JsonValueSerializer src, BeanProperty property,
88108
{
89109
super(_notNullClass(src.handledType()));
90110
_accessor = src._accessor;
111+
_valueType = src._valueType;
91112
_valueSerializer = (JsonSerializer<Object>) ser;
92113
_property = property;
93114
_forceTypeInformation = forceTypeInfo;
115+
_dynamicSerializers = PropertySerializerMap.emptyForProperties();
94116
}
95117

96118
@SuppressWarnings("unchecked")
@@ -107,7 +129,41 @@ public JsonValueSerializer withResolved(BeanProperty property,
107129
}
108130
return new JsonValueSerializer(this, property, ser, forceTypeInfo);
109131
}
110-
132+
133+
/*
134+
/**********************************************************
135+
/* Overrides
136+
/**********************************************************
137+
*/
138+
139+
@Override // since 2.12
140+
public boolean isEmpty(SerializerProvider ctxt, Object value0)
141+
{
142+
Object referenced = _accessor.getValue(value0);
143+
144+
if (referenced == null) {
145+
return true;
146+
}
147+
JsonSerializer<Object> ser = _valueSerializer;
148+
if (ser == null) {
149+
try {
150+
Class<?> cc = referenced.getClass();
151+
ser = _dynamicSerializers.serializerFor(cc);
152+
if (ser == null) {
153+
if (_valueType.hasGenericTypes()) {
154+
ser = _findAndAddDynamic(_dynamicSerializers,
155+
ctxt.constructSpecializedType(_valueType, cc), ctxt);
156+
} else {
157+
ser = _findAndAddDynamic(_dynamicSerializers, cc, ctxt);
158+
}
159+
}
160+
} catch (JsonMappingException e) {
161+
throw new RuntimeJsonMappingException(e);
162+
}
163+
}
164+
return ser.isEmpty(ctxt, referenced);
165+
}
166+
111167
/*
112168
/**********************************************************
113169
/* Post-processing
@@ -129,20 +185,19 @@ public JsonSerializer<?> createContextual(SerializerProvider provider,
129185
// if not, we don't really know the actual type until we get the instance.
130186

131187
// 10-Mar-2010, tatu: Except if static typing is to be used
132-
JavaType t = _accessor.getType();
133-
if (provider.isEnabled(MapperFeature.USE_STATIC_TYPING) || t.isFinal()) {
188+
if (provider.isEnabled(MapperFeature.USE_STATIC_TYPING) || _valueType.isFinal()) {
134189
// false -> no need to cache
135190
/* 10-Mar-2010, tatu: Ideally we would actually separate out type
136191
* serializer from value serializer; but, alas, there's no access
137192
* to serializer factory at this point...
138193
*/
139194
// 05-Sep-2013, tatu: I _think_ this can be considered a primary property...
140-
ser = provider.findPrimaryPropertySerializer(t, property);
195+
ser = provider.findPrimaryPropertySerializer(_valueType, property);
141196
/* 09-Dec-2010, tatu: Turns out we must add special handling for
142197
* cases where "native" (aka "natural") type is being serialized,
143198
* using standard serializer
144199
*/
145-
boolean forceTypeInformation = isNaturalTypeWithStdHandling(t.getRawClass(), ser);
200+
boolean forceTypeInformation = isNaturalTypeWithStdHandling(_valueType.getRawClass(), ser);
146201
return withResolved(property, ser, forceTypeInformation);
147202
}
148203
// [databind#2822]: better hold on to "property", regardless
@@ -164,27 +219,30 @@ public JsonSerializer<?> createContextual(SerializerProvider provider,
164219
*/
165220

166221
@Override
167-
public void serialize(Object bean, JsonGenerator gen, SerializerProvider prov) throws IOException
222+
public void serialize(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException
168223
{
169224
try {
170225
Object value = _accessor.getValue(bean);
171226
if (value == null) {
172-
prov.defaultSerializeNull(gen);
227+
provider.defaultSerializeNull(gen);
173228
return;
174229
}
175230
JsonSerializer<Object> ser = _valueSerializer;
176231
if (ser == null) {
177-
Class<?> c = value.getClass();
178-
// 10-Mar-2010, tatu: Ideally we would actually separate out type
179-
// serializer from value serializer; but, alas, there's no access
180-
// to serializer factory at this point...
181-
182-
// let's cache it, may be needed soon again
183-
ser = prov.findTypedValueSerializer(c, true, _property);
232+
Class<?> cc = value.getClass();
233+
ser = _dynamicSerializers.serializerFor(cc);
234+
if (ser == null) {
235+
if (_valueType.hasGenericTypes()) {
236+
ser = _findAndAddDynamic(_dynamicSerializers,
237+
provider.constructSpecializedType(_valueType, cc), provider);
238+
} else {
239+
ser = _findAndAddDynamic(_dynamicSerializers, cc, provider);
240+
}
241+
}
184242
}
185-
ser.serialize(value, gen, prov);
243+
ser.serialize(value, gen, provider);
186244
} catch (Exception e) {
187-
wrapAndThrow(prov, e, bean, _accessor.getName() + "()");
245+
wrapAndThrow(provider, e, bean, _accessor.getName() + "()");
188246
}
189247
}
190248

@@ -203,7 +261,16 @@ public void serializeWithType(Object bean, JsonGenerator gen, SerializerProvider
203261
}
204262
JsonSerializer<Object> ser = _valueSerializer;
205263
if (ser == null) { // no serializer yet? Need to fetch
206-
ser = provider.findValueSerializer(value.getClass(), _property);
264+
Class<?> cc = value.getClass();
265+
ser = _dynamicSerializers.serializerFor(cc);
266+
if (ser == null) {
267+
if (_valueType.hasGenericTypes()) {
268+
ser = _findAndAddDynamic(_dynamicSerializers,
269+
provider.constructSpecializedType(_valueType, cc), provider);
270+
} else {
271+
ser = _findAndAddDynamic(_dynamicSerializers, cc, provider);
272+
}
273+
}
207274
} else {
208275
// 09-Dec-2010, tatu: To work around natural type's refusal to add type info, we do
209276
// this (note: type is for the wrapper type, not enclosed value!)
@@ -219,7 +286,7 @@ public void serializeWithType(Object bean, JsonGenerator gen, SerializerProvider
219286
}
220287
// 28-Sep-2016, tatu: As per [databind#1385], we do need to do some juggling
221288
// to use different Object for type id (logical type) and actual serialization
222-
// (delegat type).
289+
// (delegate type).
223290
TypeSerializerRerouter rr = new TypeSerializerRerouter(typeSer0, bean);
224291
ser.serializeWithType(value, gen, provider, rr);
225292
} catch (Exception e) {
@@ -251,7 +318,6 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t
251318
*
252319
* Note that meaning of JsonValue, then, is very different for Enums. Sigh.
253320
*/
254-
final JavaType type = _accessor.getType();
255321
Class<?> declaring = _accessor.getDeclaringClass();
256322
if ((declaring != null) && ClassUtil.isEnumType(declaring)) {
257323
if (_acceptJsonFormatVisitorForEnum(visitor, typeHint, declaring)) {
@@ -260,13 +326,13 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t
260326
}
261327
JsonSerializer<Object> ser = _valueSerializer;
262328
if (ser == null) {
263-
ser = visitor.getProvider().findTypedValueSerializer(type, false, _property);
329+
ser = visitor.getProvider().findTypedValueSerializer(_valueType, false, _property);
264330
if (ser == null) { // can this ever occur?
265331
visitor.expectAnyFormat(typeHint);
266332
return;
267333
}
268334
}
269-
ser.acceptJsonFormatVisitor(visitor, type);
335+
ser.acceptJsonFormatVisitor(visitor, _valueType);
270336
}
271337

272338
/**
@@ -322,6 +388,34 @@ protected boolean isNaturalTypeWithStdHandling(Class<?> rawType, JsonSerializer<
322388
return isDefaultSerializer(ser);
323389
}
324390

391+
// @since 2.12
392+
protected final JsonSerializer<Object> _findAndAddDynamic(PropertySerializerMap map,
393+
Class<?> type, SerializerProvider provider) throws JsonMappingException
394+
{
395+
// 31-Oct-2020, tatu: Should not get typed/root serializer, but for now has to do:
396+
JsonSerializer<Object> serializer = provider.findTypedValueSerializer(type, false, _property);
397+
PropertySerializerMap.SerializerAndMapResult result = _dynamicSerializers.addSerializer(type, serializer);
398+
// did we get a new map of serializers? If so, start using it
399+
if (map != result.map) {
400+
_dynamicSerializers = result.map;
401+
}
402+
return serializer;
403+
}
404+
405+
// @since 2.12
406+
protected final JsonSerializer<Object> _findAndAddDynamic(PropertySerializerMap map,
407+
JavaType type, SerializerProvider provider) throws JsonMappingException
408+
{
409+
// 31-Oct-2020, tatu: Should not get typed/root serializer, but for now has to do:
410+
JsonSerializer<Object> serializer = provider.findTypedValueSerializer(type, false, _property);
411+
PropertySerializerMap.SerializerAndMapResult result = _dynamicSerializers.addSerializer(type, serializer);
412+
// did we get a new map of serializers? If so, start using it
413+
if (map != result.map) {
414+
_dynamicSerializers = result.map;
415+
}
416+
return serializer;
417+
}
418+
325419
/*
326420
/**********************************************************
327421
/* Other methods

src/test/java/com/fasterxml/jackson/databind/deser/creators/TestPolymorphicDelegating.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ public void testAbstractDelegateWithCreator() throws Exception
4343
Issue580Bean input = new Issue580Bean(new Issue580Impl(13));
4444
ObjectMapper mapper = new ObjectMapper();
4545
String json = mapper.writeValueAsString(input);
46-
4746
Issue580Bean result = mapper.readValue(json, Issue580Bean.class);
4847
assertNotNull(result);
4948
assertNotNull(result.value);

src/test/java/com/fasterxml/jackson/databind/ser/filter/MapInclusionTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.*;
55

66
import com.fasterxml.jackson.annotation.JsonInclude;
7+
import com.fasterxml.jackson.annotation.JsonValue;
78
import com.fasterxml.jackson.databind.*;
89

910
public class MapInclusionTest extends BaseMapTest
@@ -41,6 +42,17 @@ public NoNullsNotEmptyMapContainer add(String key, String value) {
4142
}
4243
}
4344

45+
// [databind#2909]
46+
static class Wrapper2909 {
47+
@JsonValue
48+
public Map<String, String> values = new HashMap<>();
49+
}
50+
51+
static class TopLevel2099 {
52+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
53+
public Wrapper2909 nested = new Wrapper2909();
54+
}
55+
4456
/*
4557
/**********************************************************
4658
/* Test methods
@@ -80,4 +92,10 @@ public void testNonEmptyNoNullsMap() throws IOException
8092
.add("b", null));
8193
assertEquals(aposToQuotes("{}"), json);
8294
}
95+
96+
// [databind#2909]
97+
public void testMapViaJsonValue() throws Exception
98+
{
99+
assertEquals(a2q("{}"), MAPPER.writeValueAsString(new TopLevel2099()));
100+
}
83101
}

0 commit comments

Comments
 (0)