Skip to content

Commit d0daf23

Browse files
committed
Fix #1428
1 parent 2f76bc8 commit d0daf23

File tree

10 files changed

+154
-102
lines changed

10 files changed

+154
-102
lines changed

release-notes/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Project: jackson-databind
3030
#1399: Add support for `@JsonSetter(merge=OptBoolean.TRUE`) to allow "deep update"
3131
#1406: `ObjectMapper.readTree()` methods do not return `null` on end-of-input
3232
(reported by Fabrizio C)
33+
#1428: Allow `@JsonValue` on a field, not just getter
3334
#1434: Explicitly pass null on invoke calls with no arguments
3435
(contributed by Emiliano C)
3536
#1433: `ObjectMapper.convertValue()` with null does not consider null conversions

src/main/java/com/fasterxml/jackson/databind/BeanDescription.java

+3
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ protected BeanDescription(JavaType type) {
184184
* if any. If multiple ones are found,
185185
* an error is reported by throwing {@link IllegalArgumentException}
186186
*/
187+
public abstract AnnotatedMember findJsonValueAccessor();
188+
189+
@Deprecated // since 2.9
187190
public abstract AnnotatedMethod findJsonValueMethod();
188191

189192
public abstract AnnotatedMethod findMethod(String name, Class<?>[] paramTypes);

src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.fasterxml.jackson.databind.deser;
22

3-
import java.lang.reflect.Method;
43
import java.util.*;
54
import java.util.concurrent.*;
65
import java.util.concurrent.atomic.AtomicReference;
@@ -1243,7 +1242,7 @@ public JsonDeserializer<?> createEnumDeserializer(DeserializationContext ctxt,
12431242
// Need to consider @JsonValue if one found
12441243
if (deser == null) {
12451244
deser = new EnumDeserializer(constructEnumResolver(enumClass,
1246-
config, beanDesc.findJsonValueMethod()));
1245+
config, beanDesc.findJsonValueAccessor()));
12471246
}
12481247
}
12491248

@@ -1432,7 +1431,7 @@ private KeyDeserializer _createEnumKeyDeserializer(DeserializationContext ctxt,
14321431
return StdKeyDeserializers.constructDelegatingKeyDeserializer(config, type, valueDesForKey);
14331432
}
14341433
}
1435-
EnumResolver enumRes = constructEnumResolver(enumClass, config, beanDesc.findJsonValueMethod());
1434+
EnumResolver enumRes = constructEnumResolver(enumClass, config, beanDesc.findJsonValueAccessor());
14361435
// May have @JsonCreator for static factory method:
14371436
for (AnnotatedMethod factory : beanDesc.getFactoryMethods()) {
14381437
if (_hasCreatorAnnotation(ctxt, factory)) {
@@ -1863,14 +1862,15 @@ protected JavaType resolveMemberAndTypeAnnotations(DeserializationContext ctxt,
18631862
}
18641863

18651864
protected EnumResolver constructEnumResolver(Class<?> enumClass,
1866-
DeserializationConfig config, AnnotatedMethod jsonValueMethod)
1865+
DeserializationConfig config, AnnotatedMember jsonValueAccessor)
18671866
{
1868-
if (jsonValueMethod != null) {
1869-
Method accessor = jsonValueMethod.getAnnotated();
1867+
if (jsonValueAccessor != null) {
18701868
if (config.canOverrideAccessModifiers()) {
1871-
ClassUtil.checkAndFixAccess(accessor, config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
1869+
ClassUtil.checkAndFixAccess(jsonValueAccessor.getMember(),
1870+
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
18721871
}
1873-
return EnumResolver.constructUnsafeUsingMethod(enumClass, accessor, config.getAnnotationIntrospector());
1872+
return EnumResolver.constructUnsafeUsingMethod(enumClass,
1873+
jsonValueAccessor, config.getAnnotationIntrospector());
18741874
}
18751875
// 14-Mar-2016, tatu: We used to check `DeserializationFeature.READ_ENUMS_USING_TO_STRING`
18761876
// here, but that won't do: it must be dynamically changeable...

src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java

+7
Original file line numberDiff line numberDiff line change
@@ -240,11 +240,18 @@ public List<BeanPropertyDefinition> findProperties() {
240240
}
241241

242242
@Override
243+
@Deprecated // since 2.9
243244
public AnnotatedMethod findJsonValueMethod() {
244245
return (_propCollector == null) ? null
245246
: _propCollector.getJsonValueMethod();
246247
}
247248

249+
@Override // since 2.9
250+
public AnnotatedMember findJsonValueAccessor() {
251+
return (_propCollector == null) ? null
252+
: _propCollector.getJsonValueAccessor();
253+
}
254+
248255
@Override
249256
public Set<String> getIgnoredPropertyNames() {
250257
Set<String> ign = (_propCollector == null) ? null

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java

+51-23
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,10 @@ public class POJOPropertiesCollector
9090

9191
/**
9292
* Method(s) marked with 'JsonValue' annotation
93+
*<p>
94+
* NOTE: before 2.9, was `AnnotatedMethod`; with 2.9 allows fields too
9395
*/
94-
protected LinkedList<AnnotatedMethod> _jsonValueGetters;
96+
protected LinkedList<AnnotatedMember> _jsonValueAccessors;
9597

9698
/**
9799
* Lazily collected list of properties that can be implicitly
@@ -166,20 +168,32 @@ public Map<Object, AnnotatedMember> getInjectables() {
166168
}
167169
return _injectables;
168170
}
169-
170-
public AnnotatedMethod getJsonValueMethod()
171+
172+
@Deprecated // since 2.9
173+
public AnnotatedMethod getJsonValueMethod() {
174+
AnnotatedMember m = getJsonValueAccessor();
175+
if (m instanceof AnnotatedMethod) {
176+
return (AnnotatedMethod) m;
177+
}
178+
return null;
179+
}
180+
181+
/**
182+
* @since 2.9
183+
*/
184+
public AnnotatedMember getJsonValueAccessor()
171185
{
172186
if (!_collected) {
173187
collectAll();
174188
}
175189
// If @JsonValue defined, must have a single one
176-
if (_jsonValueGetters != null) {
177-
if (_jsonValueGetters.size() > 1) {
178-
reportProblem("Multiple value properties defined ("+_jsonValueGetters.get(0)+" vs "
179-
+_jsonValueGetters.get(1)+")");
190+
if (_jsonValueAccessors != null) {
191+
if (_jsonValueAccessors.size() > 1) {
192+
reportProblem("Multiple 'as-value' properties defined ("+_jsonValueAccessors.get(0)+" vs "
193+
+_jsonValueAccessors.get(1)+")");
180194
}
181195
// otherwise we won't greatly care
182-
return _jsonValueGetters.get(0);
196+
return _jsonValueAccessors.get(0);
183197
}
184198
return null;
185199
}
@@ -367,7 +381,29 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
367381
final boolean transientAsIgnoral = _config.isEnabled(MapperFeature.PROPAGATE_TRANSIENT_MARKER);
368382

369383
for (AnnotatedField f : _classDef.fields()) {
370-
String implName = (ai == null) ? null : ai.findImplicitPropertyName(f);
384+
String implName;
385+
if (ai == null) {
386+
implName = null;
387+
} else {
388+
implName = ai.findImplicitPropertyName(f);
389+
// @JsonValue?
390+
if (Boolean.TRUE.equals(ai.findAsValueAnnotation(f))) {
391+
if (_jsonValueAccessors == null) {
392+
_jsonValueAccessors = new LinkedList<>();
393+
}
394+
_jsonValueAccessors.add(f);
395+
continue;
396+
}
397+
// @JsonAnySetter?
398+
// !!! 20-Nov-2016, tatu: This is wrong; needs to go via AnnotationIntrospector!
399+
if (f.hasAnnotation(JsonAnySetter.class)) {
400+
if (_anySetterField == null) {
401+
_anySetterField = new LinkedList<AnnotatedMember>();
402+
}
403+
_anySetterField.add(f);
404+
continue;
405+
}
406+
}
371407
if (implName == null) {
372408
implName = f.getName();
373409
}
@@ -417,18 +453,10 @@ protected void _addFields(Map<String, POJOPropertyBuilder> props)
417453
* Also: if 'ignored' is set, need to included until a later point, to
418454
* avoid losing ignoral information.
419455
*/
420-
if (pruneFinalFields && (pn == null) && !ignored && Modifier.isFinal(f.getModifiers())) {
456+
if (pruneFinalFields && (pn == null) && !ignored
457+
&& Modifier.isFinal(f.getModifiers())) {
421458
continue;
422459
}
423-
424-
//if field has annotation @JsonAnySetter
425-
if(f.hasAnnotation(JsonAnySetter.class)) {
426-
if (_anySetterField == null) {
427-
_anySetterField = new LinkedList<AnnotatedMember>();
428-
}
429-
_anySetterField.add(f);
430-
}
431-
432460
_property(props, implName).addField(f, pn, nameExplicit, visible, ignored);
433461
}
434462
}
@@ -550,11 +578,11 @@ protected void _addGetterMethod(Map<String, POJOPropertyBuilder> props,
550578
return;
551579
}
552580
// @JsonValue?
553-
if (ai.hasAsValueAnnotation(m)) {
554-
if (_jsonValueGetters == null) {
555-
_jsonValueGetters = new LinkedList<AnnotatedMethod>();
581+
if (Boolean.TRUE.equals(ai.findAsValueAnnotation(m))) {
582+
if (_jsonValueAccessors == null) {
583+
_jsonValueAccessors = new LinkedList<>();
556584
}
557-
_jsonValueGetters.add(m);
585+
_jsonValueAccessors.add(m);
558586
return;
559587
}
560588
}

src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.fasterxml.jackson.databind.ser;
22

3-
import java.lang.reflect.Method;
43
import java.math.BigDecimal;
54
import java.math.BigInteger;
65
import java.net.InetAddress;
@@ -224,14 +223,14 @@ public JsonSerializer<Object> createKeySerializer(SerializationConfig config,
224223
// As per [databind#47], also need to support @JsonValue
225224
if (ser == null) {
226225
beanDesc = config.introspect(keyType);
227-
AnnotatedMethod am = beanDesc.findJsonValueMethod();
226+
AnnotatedMember am = beanDesc.findJsonValueAccessor();
228227
if (am != null) {
229-
final Class<?> rawType = am.getRawReturnType();
228+
final Class<?> rawType = am.getRawType();
230229
JsonSerializer<?> delegate = StdKeySerializers.getStdKeySerializer(config,
231230
rawType, true);
232-
Method m = am.getAnnotated();
233231
if (config.canOverrideAccessModifiers()) {
234-
ClassUtil.checkAndFixAccess(m, config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
232+
ClassUtil.checkAndFixAccess(am.getMember(),
233+
config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
235234
}
236235
ser = new JsonValueSerializer(am, delegate);
237236
} else {
@@ -345,14 +344,14 @@ protected final JsonSerializer<?> findSerializerByAnnotations(SerializerProvider
345344
return SerializableSerializer.instance;
346345
}
347346
// Second: @JsonValue for any type
348-
AnnotatedMethod valueMethod = beanDesc.findJsonValueMethod();
349-
if (valueMethod != null) {
350-
Method m = valueMethod.getAnnotated();
347+
AnnotatedMember valueAccessor = beanDesc.findJsonValueAccessor();
348+
if (valueAccessor != null) {
351349
if (prov.canOverrideAccessModifiers()) {
352-
ClassUtil.checkAndFixAccess(m, prov.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
350+
ClassUtil.checkAndFixAccess(valueAccessor.getMember(),
351+
prov.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
353352
}
354-
JsonSerializer<Object> ser = findSerializerFromAnnotation(prov, valueMethod);
355-
return new JsonValueSerializer(valueMethod, ser);
353+
JsonSerializer<Object> ser = findSerializerFromAnnotation(prov, valueAccessor);
354+
return new JsonValueSerializer(valueAccessor, ser);
356355
}
357356
// No well-known annotations...
358357
return null;

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

+18-18
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import com.fasterxml.jackson.core.*;
1111
import com.fasterxml.jackson.databind.*;
1212
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
13-
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
13+
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
1414
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
1515
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
1616
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
@@ -38,11 +38,11 @@
3838
public class JsonValueSerializer
3939
extends StdSerializer<Object>
4040
implements ContextualSerializer, JsonFormatVisitable, SchemaAware
41-
{
41+
{
4242
/**
43-
* @since 2.8 (was "plain" method before)
43+
* @since 2.9
4444
*/
45-
protected final AnnotatedMethod _accessorMethod;
45+
protected final AnnotatedMember _accessor;
4646

4747
protected final JsonSerializer<Object> _valueSerializer;
4848

@@ -72,10 +72,10 @@ public class JsonValueSerializer
7272
* to information we need
7373
*/
7474
@SuppressWarnings("unchecked")
75-
public JsonValueSerializer(AnnotatedMethod valueMethod, JsonSerializer<?> ser)
75+
public JsonValueSerializer(AnnotatedMember accessor, JsonSerializer<?> ser)
7676
{
77-
super(valueMethod.getType());
78-
_accessorMethod = valueMethod;
77+
super(accessor.getType());
78+
_accessor = accessor;
7979
_valueSerializer = (JsonSerializer<Object>) ser;
8080
_property = null;
8181
_forceTypeInformation = true; // gets reconsidered when we are contextualized
@@ -86,7 +86,7 @@ public JsonValueSerializer(JsonValueSerializer src, BeanProperty property,
8686
JsonSerializer<?> ser, boolean forceTypeInfo)
8787
{
8888
super(_notNullClass(src.handledType()));
89-
_accessorMethod = src._accessorMethod;
89+
_accessor = src._accessor;
9090
_valueSerializer = (JsonSerializer<Object>) ser;
9191
_property = property;
9292
_forceTypeInformation = forceTypeInfo;
@@ -128,7 +128,7 @@ public JsonSerializer<?> createContextual(SerializerProvider provider,
128128
* if not, we don't really know the actual type until we get the instance.
129129
*/
130130
// 10-Mar-2010, tatu: Except if static typing is to be used
131-
JavaType t = _accessorMethod.getType();
131+
JavaType t = _accessor.getType();
132132
if (provider.isEnabled(MapperFeature.USE_STATIC_TYPING) || t.isFinal()) {
133133
// false -> no need to cache
134134
/* 10-Mar-2010, tatu: Ideally we would actually separate out type
@@ -162,7 +162,7 @@ public JsonSerializer<?> createContextual(SerializerProvider provider,
162162
public void serialize(Object bean, JsonGenerator gen, SerializerProvider prov) throws IOException
163163
{
164164
try {
165-
Object value = _accessorMethod.getValue(bean);
165+
Object value = _accessor.getValue(bean);
166166
if (value == null) {
167167
prov.defaultSerializeNull(gen);
168168
return;
@@ -179,7 +179,7 @@ public void serialize(Object bean, JsonGenerator gen, SerializerProvider prov) t
179179
}
180180
ser.serialize(value, gen, prov);
181181
} catch (Exception e) {
182-
wrapAndThrow(prov, e, bean, _accessorMethod.getName() + "()");
182+
wrapAndThrow(prov, e, bean, _accessor.getName() + "()");
183183
}
184184
}
185185

@@ -190,7 +190,7 @@ public void serializeWithType(Object bean, JsonGenerator gen, SerializerProvider
190190
// Regardless of other parts, first need to find value to serialize:
191191
Object value = null;
192192
try {
193-
value = _accessorMethod.getValue(bean);
193+
value = _accessor.getValue(bean);
194194
// and if we got null, can also just write it directly
195195
if (value == null) {
196196
provider.defaultSerializeNull(gen);
@@ -217,7 +217,7 @@ public void serializeWithType(Object bean, JsonGenerator gen, SerializerProvider
217217
TypeSerializerRerouter rr = new TypeSerializerRerouter(typeSer0, bean);
218218
ser.serializeWithType(value, gen, provider, rr);
219219
} catch (Exception e) {
220-
wrapAndThrow(provider, e, bean, _accessorMethod.getName() + "()");
220+
wrapAndThrow(provider, e, bean, _accessor.getName() + "()");
221221
}
222222
}
223223

@@ -245,8 +245,8 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t
245245
*
246246
* Note that meaning of JsonValue, then, is very different for Enums. Sigh.
247247
*/
248-
final JavaType type = _accessorMethod.getType();
249-
Class<?> declaring = _accessorMethod.getDeclaringClass();
248+
final JavaType type = _accessor.getType();
249+
Class<?> declaring = _accessor.getDeclaringClass();
250250
if ((declaring != null) && declaring.isEnum()) {
251251
if (_acceptJsonFormatVisitorForEnum(visitor, typeHint, declaring)) {
252252
return;
@@ -285,14 +285,14 @@ protected boolean _acceptJsonFormatVisitorForEnum(JsonFormatVisitorWrapper visit
285285
// 21-Apr-2016, tatu: This is convoluted to the max, but essentially we
286286
// call `@JsonValue`-annotated accessor method on all Enum members,
287287
// so it all "works out". To some degree.
288-
enums.add(String.valueOf(_accessorMethod.callOn(en)));
288+
enums.add(String.valueOf(_accessor.getValue(en)));
289289
} catch (Exception e) {
290290
Throwable t = e;
291291
while (t instanceof InvocationTargetException && t.getCause() != null) {
292292
t = t.getCause();
293293
}
294294
ClassUtil.throwIfError(t);
295-
throw JsonMappingException.wrapWithPath(t, en, _accessorMethod.getName() + "()");
295+
throw JsonMappingException.wrapWithPath(t, en, _accessor.getName() + "()");
296296
}
297297
}
298298
stringVisitor.enumTypes(enums);
@@ -324,7 +324,7 @@ protected boolean isNaturalTypeWithStdHandling(Class<?> rawType, JsonSerializer<
324324

325325
@Override
326326
public String toString() {
327-
return "(@JsonValue serializer for method " + _accessorMethod.getDeclaringClass() + "#" + _accessorMethod.getName() + ")";
327+
return "(@JsonValue serializer for method " + _accessor.getDeclaringClass() + "#" + _accessor.getName() + ")";
328328
}
329329

330330
/*

0 commit comments

Comments
 (0)