diff --git a/release-notes/CREDITS b/release-notes/CREDITS index cf8f03433b..875b7ffd3c 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -221,7 +221,12 @@ Dylan Scott (dylanscott@github) issue) (2.5.2) +Dmitry Spikhalskiy (Spikhalskiy@github) + * Reported #731, suggested the way to fix it: XmlAdapter result marshaling error in + case of ValueType=Object + (2.5.3) + John Meyer (jpmeyer@github) - * Reported, contributed fix for #745: EnumDeserializer.deserializerForCreator fails + * Reported, contributed fix for #745: EnumDeserializer.deserializerForCreator() fails when used to deserialize a Map key (2.5.3) diff --git a/release-notes/VERSION b/release-notes/VERSION index 8856d08bd9..cc413c95eb 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -6,6 +6,8 @@ Project: jackson-databind 2.5.3 (not yet released) +#731: XmlAdapter result marshaling error in case of ValueType=Object + (reported, debugged by Dmitry S) #742: Allow deserialization of `null` Object Id (missing already allowed) #744: Custom deserializer with parent object update failing (reported by migel@github) diff --git a/src/main/java/com/fasterxml/jackson/databind/JavaType.java b/src/main/java/com/fasterxml/jackson/databind/JavaType.java index 822130b007..07b6ba9683 100644 --- a/src/main/java/com/fasterxml/jackson/databind/JavaType.java +++ b/src/main/java/com/fasterxml/jackson/databind/JavaType.java @@ -290,6 +290,17 @@ public boolean isConcrete() { @Override public boolean isMapLikeType() { return false; } + /** + * Convenience method, short-hand for + * + * getRawClass() == Object.class + * + * and used to figure if we basically have "untyped" type object. + * + * @since 2.5 + */ + public final boolean isJavaLangObject() { return _class == Object.class; } + /** * Accessor for checking whether handlers for dealing with values of * this type should use static typing (as opposed to dynamic typing). @@ -299,7 +310,7 @@ public boolean isConcrete() { * @since 2.2 */ public final boolean useStaticType() { return _asStatic; } - + /* /********************************************************** /* Public API, type parameter access; pass-through diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java index ec540cc6d8..14e6d9345c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java @@ -159,7 +159,8 @@ public JsonSerializer createSerializer(SerializerProvider prov, // [#359]: explicitly check (again) for @JsonSerializer... ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo()); } - if (ser == null) { + // [databind#731]: Should skip if nominally java.lang.Object + if (ser == null && !delegateType.isJavaLangObject()) { ser = _createSerializer2(prov, delegateType, beanDesc, true); } return new StdDelegatingSerializer(conv, delegateType, ser); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java index ee8735c2fb..3e11648086 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java @@ -366,7 +366,9 @@ protected JsonSerializer findConvertingSerializer(SerializerProvider pro if (convDef != null) { Converter conv = provider.converterInstance(prop.getMember(), convDef); JavaType delegateType = conv.getOutputType(provider.getTypeFactory()); - JsonSerializer ser = provider.findValueSerializer(delegateType, prop); + // [databind#731]: Should skip if nominally java.lang.Object + JsonSerializer ser = delegateType.isJavaLangObject() ? null + : provider.findValueSerializer(delegateType, prop); return new StdDelegatingSerializer(conv, delegateType, ser); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java index 0b435ead16..6c52b736ec 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java @@ -115,13 +115,20 @@ public JsonSerializer createContextual(SerializerProvider provider, BeanPrope if (delegateType == null) { delegateType = _converter.getOutputType(provider.getTypeFactory()); } - delSer = provider.findValueSerializer(delegateType); + /* 02-Apr-2015, tatu: For "dynamic case", where type is only specified as + * java.lang.Object (or missing generic), [databind#731] + */ + if (!delegateType.isJavaLangObject()) { + delSer = provider.findValueSerializer(delegateType); + } } if (delSer instanceof ContextualSerializer) { delSer = provider.handleSecondaryContextualization(delSer, property); } - return (delSer == _delegateSerializer) ? this - : withDelegate(_converter, delegateType, delSer); + if (delSer == _delegateSerializer && delegateType == _delegateType) { + return this; + } + return withDelegate(_converter, delegateType, delSer); } /* @@ -154,7 +161,12 @@ public void serialize(Object value, JsonGenerator gen, SerializerProvider provid provider.defaultSerializeNull(gen); return; } - _delegateSerializer.serialize(delegateValue, gen, provider); + // 02-Apr-2015, tatu: As per [databind#731] may need to do dynamic lookup + JsonSerializer ser = _delegateSerializer; + if (ser == null) { + ser = _findSerializer(delegateValue, provider); + } + ser.serialize(delegateValue, gen, provider); } @Override @@ -165,14 +177,21 @@ public void serializeWithType(Object value, JsonGenerator gen, SerializerProvide * let's give it a chance? */ Object delegateValue = convertValue(value); - _delegateSerializer.serializeWithType(delegateValue, gen, provider, typeSer); + JsonSerializer ser = _delegateSerializer; + if (ser == null) { + ser = _findSerializer(value, provider); + } + ser.serializeWithType(delegateValue, gen, provider, typeSer); } @Override - @Deprecated // since 1.5 + @Deprecated // since 2.5 public boolean isEmpty(Object value) { Object delegateValue = convertValue(value); + if (_delegateSerializer == null) { // best we can do for now, too costly to look up + return (value == null); + } return _delegateSerializer.isEmpty(delegateValue); } @@ -180,6 +199,9 @@ public boolean isEmpty(Object value) public boolean isEmpty(SerializerProvider prov, Object value) { Object delegateValue = convertValue(value); + if (_delegateSerializer == null) { // best we can do for now, too costly to look up + return (value == null); + } return _delegateSerializer.isEmpty(prov, delegateValue); } @@ -216,7 +238,10 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t /* 03-Sep-2012, tatu: Not sure if this can be made to really work * properly... but for now, try this: */ - _delegateSerializer.acceptJsonFormatVisitor(visitor, typeHint); + // 02-Apr-2015, tatu: For dynamic case, very little we can do + if (_delegateSerializer != null) { + _delegateSerializer.acceptJsonFormatVisitor(visitor, typeHint); + } } /* @@ -239,4 +264,19 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t protected Object convertValue(Object value) { return _converter.convert(value); } + + /** + * Helper method used for locating serializer to use in dynamic use case, where + * actual type value gets converted to is not specified beyond basic + * {@link java.lang.Object}, and where serializer needs to be located dynamically + * based on actual value type. + * + * @since 2.6 + */ + protected JsonSerializer _findSerializer(Object value, SerializerProvider serializers) + throws JsonMappingException + { + // NOTE: will NOT call contextualization + return serializers.findValueSerializer(value.getClass()); + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java index 31bbe0870a..6089e6be2b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java @@ -250,7 +250,6 @@ protected JsonSerializer findConvertingContentSerializer(SerializerProvider p * when applying contextual content converter; this is not ideal way, * but should work for most cases. */ - final AnnotationIntrospector intr = provider.getAnnotationIntrospector(); if (intr != null && prop != null) { AnnotatedMember m = prop.getMember(); @@ -259,7 +258,8 @@ protected JsonSerializer findConvertingContentSerializer(SerializerProvider p if (convDef != null) { Converter conv = provider.converterInstance(prop.getMember(), convDef); JavaType delegateType = conv.getOutputType(provider.getTypeFactory()); - if (existingSerializer == null) { + // [databind#731]: Should skip if nominally java.lang.Object + if (existingSerializer == null && !delegateType.hasRawClass(Object.class)) { existingSerializer = provider.findValueSerializer(delegateType); } return new StdDelegatingSerializer(conv, delegateType, existingSerializer); diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingSerializer.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingSerializer.java index e9e467ddea..6104782c65 100644 --- a/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingSerializer.java +++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestConvertingSerializer.java @@ -122,7 +122,33 @@ public void serialize(Target a, JsonGenerator jsonGenerator, SerializerProvider jsonGenerator.writeString("Target"); } } - + + // [Issue#731] + public static class DummyBean { + public final int a, b; + public DummyBean(int v1, int v2) { + a = v1 * 2; + b = v2 * 2; + } + } + + @JsonSerialize(converter = UntypedConvertingBeanConverter.class) + static class ConvertingBeanWithUntypedConverter { + public int x, y; + public ConvertingBeanWithUntypedConverter(int v1, int v2) { + x = v1; + y = v2; + } + } + + static class UntypedConvertingBeanConverter extends StdConverter + { + @Override + public Object convert(ConvertingBeanWithUntypedConverter cb) { + return new DummyBean(cb.x, cb.y); + } + } + /* /********************************************************** /* Test methods @@ -168,4 +194,12 @@ public void testIssue359() throws Exception { String json = objectWriter().writeValueAsString(new Bean359()); assertEquals("{\"stuff\":[\"Target\"]}", json); } + + // [databind#731]: Problems converting from java.lang.Object ("unknown") + public void testIssue731() throws Exception + { + String json = objectWriter().writeValueAsString(new ConvertingBeanWithUntypedConverter(1, 2)); + // must be {"a":2,"b":4} + assertEquals("{\"a\":2,\"b\":4}", json); + } }