From e9df8155f4f0a1f6bbc769deb3e791af39ad4573 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 21 Apr 2016 12:28:28 -0700 Subject: [PATCH] Fix #1211 --- release-notes/VERSION | 1 + .../databind/introspect/AnnotatedMethod.java | 14 +++- .../databind/ser/BasicSerializerFactory.java | 4 +- .../databind/ser/std/JsonValueSerializer.java | 65 +++++++++---------- .../databind/jsonschema/NewSchemaTest.java | 3 +- 5 files changed, 48 insertions(+), 39 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index 706dab94b4..422747e0f5 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -19,6 +19,7 @@ Project: jackson-databind #1182: Add the ability to specify the initial capacity of the ArrayNode (suggested by Matt V, mveitas@github) #1187: Refactor `AtomicReferenceDeserializer` into `ReferenceTypeDeserializer` +#1211: Change `JsonValueSerializer` to get `AnnotatedMethod`, not "raw" method 2.7.4 (not yet released) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java index 61b9639592..145835ec4a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedMethod.java @@ -114,7 +114,15 @@ public final Object call(Object[] args) throws Exception { public final Object call1(Object arg) throws Exception { return _method.invoke(null, arg); } - + + public final Object callOn(Object pojo) throws Exception { + return _method.invoke(pojo); + } + + public final Object callOnWith(Object pojo, Object... args) throws Exception { + return _method.invoke(pojo, args); + } + /* /******************************************************** /* AnnotatedMember impl @@ -154,7 +162,7 @@ public Object getValue(Object pojo) throws IllegalArgumentException +getFullName()+": "+e.getMessage(), e); } } - + /* /***************************************************** /* Extended API, generic @@ -170,7 +178,7 @@ public String getFullName() { return getDeclaringClass().getName() + "#" + getName() + "(" +getParameterCount()+" params)"; } - + public Class[] getRawParameterTypes() { if (_paramClasses == null) { diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java index dc970e87ff..7a32a55349 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java @@ -234,7 +234,7 @@ public JsonSerializer createKeySerializer(SerializationConfig config, if (config.canOverrideAccessModifiers()) { ClassUtil.checkAndFixAccess(m, config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } - ser = new JsonValueSerializer(m, delegate); + ser = new JsonValueSerializer(am, delegate); } else { ser = StdKeySerializers.getFallbackKeySerializer(config, keyType.getRawClass()); } @@ -353,7 +353,7 @@ protected final JsonSerializer findSerializerByAnnotations(SerializerProvider ClassUtil.checkAndFixAccess(m, prov.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } JsonSerializer ser = findSerializerFromAnnotation(prov, valueMethod); - return new JsonValueSerializer(m, ser); + return new JsonValueSerializer(valueMethod, ser); } // No well-known annotations... return null; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java index 817d289d08..608a7c86a6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java @@ -2,8 +2,6 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.LinkedHashSet; import java.util.Set; @@ -11,6 +9,7 @@ import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor; @@ -36,8 +35,11 @@ public class JsonValueSerializer extends StdSerializer implements ContextualSerializer, JsonFormatVisitable, SchemaAware -{ - protected final Method _accessorMethod; + { + /** + * @since 2.8 (was "plain" method before) + */ + protected final AnnotatedMethod _accessorMethod; protected final JsonSerializer _valueSerializer; @@ -62,11 +64,14 @@ public class JsonValueSerializer * occurs if and only if the "value method" was annotated with * {@link com.fasterxml.jackson.databind.annotation.JsonSerialize#using}), otherwise * null + * + * @since 2.8 Earlier method took "raw" Method, but that does not work with access + * to information we need */ @SuppressWarnings("unchecked") - public JsonValueSerializer(Method valueMethod, JsonSerializer ser) + public JsonValueSerializer(AnnotatedMethod valueMethod, JsonSerializer ser) { - super(valueMethod.getReturnType(), false); + super(valueMethod.getType()); _accessorMethod = valueMethod; _valueSerializer = (JsonSerializer) ser; _property = null; @@ -120,9 +125,8 @@ public JsonSerializer createContextual(SerializerProvider provider, * if not, we don't really know the actual type until we get the instance. */ // 10-Mar-2010, tatu: Except if static typing is to be used - if (provider.isEnabled(MapperFeature.USE_STATIC_TYPING) - || Modifier.isFinal(_accessorMethod.getReturnType().getModifiers())) { - JavaType t = provider.constructType(_accessorMethod.getGenericReturnType()); + JavaType t = _accessorMethod.getType(); + if (provider.isEnabled(MapperFeature.USE_STATIC_TYPING) || t.isFinal()) { // false -> no need to cache /* 10-Mar-2010, tatu: Ideally we would actually separate out type * serializer from value serializer; but, alas, there's no access @@ -155,7 +159,7 @@ public JsonSerializer createContextual(SerializerProvider provider, public void serialize(Object bean, JsonGenerator jgen, SerializerProvider prov) throws IOException { try { - Object value = _accessorMethod.invoke(bean); + Object value = _accessorMethod.getValue(bean); if (value == null) { prov.defaultSerializeNull(jgen); return; @@ -195,7 +199,7 @@ public void serializeWithType(Object bean, JsonGenerator jgen, SerializerProvide // Regardless of other parts, first need to find value to serialize: Object value = null; try { - value = _accessorMethod.invoke(bean); + value = _accessorMethod.getValue(bean); // and if we got null, can also just write it directly if (value == null) { provider.defaultSerializeNull(jgen); @@ -255,32 +259,25 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t throws JsonMappingException { /* 27-Apr-2015, tatu: First things first; for JSON Schema introspection, - * Enums are special, and unfortunately we will need to add special + * Enum types that use `@JsonValue` are special (but NOT necessarily + * anything else that RETURNS an enum!) + * So we will need to add special * handling here (see https://github.com/FasterXML/jackson-module-jsonSchema/issues/57 * for details). + * + * Note that meaning of JsonValue, then, is very different for Enums. Sigh. */ - Class decl = (typeHint == null) ? null : typeHint.getRawClass(); - if (decl == null) { - decl = _accessorMethod.getDeclaringClass(); - } - if ((decl != null) && (decl.isEnum())) { - if (_acceptJsonFormatVisitorForEnum(visitor, typeHint, decl)) { + final JavaType type = _accessorMethod.getType(); + Class declaring = _accessorMethod.getDeclaringClass(); + if ((declaring != null) && declaring.isEnum()) { + if (_acceptJsonFormatVisitorForEnum(visitor, typeHint, declaring)) { return; } } - JsonSerializer ser = _valueSerializer; if (ser == null) { - if (typeHint == null) { - if (_property != null) { - typeHint = _property.getType(); - } - if (typeHint == null) { - typeHint = visitor.getProvider().constructType(_handledType); - } - } - ser = visitor.getProvider().findTypedValueSerializer(typeHint, false, _property); - if (ser == null) { + ser = visitor.getProvider().findTypedValueSerializer(type, false, _property); + if (ser == null) { // can this ever occur? visitor.expectAnyFormat(typeHint); return; } @@ -290,7 +287,7 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t /** * Overridable helper method used for special case handling of schema information for - * Enums + * Enums. * * @return True if method handled callbacks; false if not; in latter case caller will * send default callbacks @@ -307,7 +304,10 @@ protected boolean _acceptJsonFormatVisitorForEnum(JsonFormatVisitorWrapper visit Set enums = new LinkedHashSet(); for (Object en : enumType.getEnumConstants()) { try { - enums.add(String.valueOf(_accessorMethod.invoke(en))); + // 21-Apr-2016, tatu: This is convoluted to the max, but essentially we + // call `@JsonValue`-annotated accessor method on all Enum members, + // so it all "works out". To some degree. + enums.add(String.valueOf(_accessorMethod.callOn(en))); } catch (Exception e) { Throwable t = e; while (t instanceof InvocationTargetException && t.getCause() != null) { @@ -322,9 +322,8 @@ protected boolean _acceptJsonFormatVisitorForEnum(JsonFormatVisitorWrapper visit stringVisitor.enumTypes(enums); } return true; - } - + protected boolean isNaturalTypeWithStdHandling(Class rawType, JsonSerializer ser) { // First: do we have a natural type being handled? diff --git a/src/test/java/com/fasterxml/jackson/databind/jsonschema/NewSchemaTest.java b/src/test/java/com/fasterxml/jackson/databind/jsonschema/NewSchemaTest.java index 57cefec693..ea31611b13 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsonschema/NewSchemaTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsonschema/NewSchemaTest.java @@ -100,7 +100,8 @@ public void format(JsonValueFormat format) { } public void testEnumWithJsonValue() throws Exception { final Set values = new TreeSet(); - MAPPER.acceptJsonFormatVisitor(TestEnumWithJsonValue.class, new JsonFormatVisitorWrapper.Base() { + MAPPER.acceptJsonFormatVisitor(TestEnumWithJsonValue.class, + new JsonFormatVisitorWrapper.Base() { @Override public JsonStringFormatVisitor expectStringFormat(JavaType type) { return new JsonStringFormatVisitor() {