From 67973b2afba08fc395b1e91f31e85d68acdc9d86 Mon Sep 17 00:00:00 2001 From: Binh Tran Date: Tue, 17 Dec 2019 14:15:14 -0500 Subject: [PATCH] Allow IonObjectMapper with class name annotation introspector to deserialize non-annotated payloads. Currently, an Ion Mapper with Classname Id resolver (and likely other kind of type id resolver) loses the ability to deserialize plain, non-annotated payload using only generic hints provided through type reference. This bug was likely because of this commit [1]. The fix here is to construct the type with full generic information instead of just using defaultImpl (which is now just a class). [1] https://github.com/FasterXML/jackson-databind/commit/596c6dd1850fbb92ee34ff64c7bc6e2aafc45207#diff-48b058c7384ce3f9fc9e7440fc7dc804L89 --- .../IonAnnotationTypeResolverBuilder.java | 2 +- ...serializerWithClassNameAnnotationTest.java | 116 ++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 ion/src/test/java/com/fasterxml/jackson/dataformat/ion/polymorphism/IonAnnotationTypeDeserializerWithClassNameAnnotationTest.java diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/polymorphism/IonAnnotationTypeResolverBuilder.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/polymorphism/IonAnnotationTypeResolverBuilder.java index a8224c768..3f6414767 100644 --- a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/polymorphism/IonAnnotationTypeResolverBuilder.java +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/polymorphism/IonAnnotationTypeResolverBuilder.java @@ -76,7 +76,7 @@ public TypeSerializer buildTypeSerializer(SerializationConfig config, JavaType b @Override public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection subtypes) { JavaType defImplType = (defaultImpl == null) ? null - : config.constructType(defaultImpl); + : config.constructSpecializedType(baseType, defaultImpl); return new IonAnnotationTypeDeserializer(baseType, typeIdResolver, null, typeIdVisible, defImplType); } diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/polymorphism/IonAnnotationTypeDeserializerWithClassNameAnnotationTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/polymorphism/IonAnnotationTypeDeserializerWithClassNameAnnotationTest.java new file mode 100644 index 000000000..fa1f007c0 --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/polymorphism/IonAnnotationTypeDeserializerWithClassNameAnnotationTest.java @@ -0,0 +1,116 @@ +package com.fasterxml.jackson.dataformat.ion.polymorphism; + +import com.amazon.ion.IonValue; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; +import com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +/** + * This test checks that {@link IonAnnotationTypeDeserializer} with {@link IonAnnotationIntrospector} expecting class + * name can still deserialize regular Json/Ion (specifically ION without class name) payloads. + * + * @author Binh Tran + */ +public class IonAnnotationTypeDeserializerWithClassNameAnnotationTest { + + private static IonValue ionValueWithoutAnnotation; + private static IonValue ionValueWithAnnotation; + private IonObjectMapper mapperUnderTest; + + @BeforeClass + public static void setupClass() throws IOException { + ClassA inner = new ClassA(); + inner.value = 42; + + ClassB outer = new ClassB<>(); + outer.content = inner; + + IonObjectMapper mapper = new IonObjectMapper(); + ionValueWithoutAnnotation = mapper.writeValueAsIonValue(outer); + + mapper = constructIomWithClassNameIdResolver(); + ionValueWithAnnotation = mapper.writeValueAsIonValue(outer); + } + + @Before + public void setup() { + // Important: since Jackson caches type resolving information, we need to create a separate mapper for testing. + mapperUnderTest = constructIomWithClassNameIdResolver(); + } + + @Test + public void testDeserializeAnnotatedPayload() throws IOException { + IonObjectMapper mapper = constructIomWithClassNameIdResolver(); + + ClassB newObj = mapper.readValue(ionValueWithAnnotation, new TypeReference>() {}); + + ClassA content = newObj.content; + assertEquals(42, content.value); + } + + @Test + public void testDeserializeNonAnnotatedPayload() throws IOException { + IonObjectMapper mapper = constructIomWithClassNameIdResolver(); + + ClassB newObj = mapper.readValue(ionValueWithoutAnnotation, new TypeReference>() {}); + + ClassA content = newObj.content; + assertEquals(42, content.value); + } + + private static IonObjectMapper constructIomWithClassNameIdResolver() { + IonObjectMapper mapper = new IonObjectMapper(); + mapper.registerModule(new IonAnnotationModule()); + + return mapper; + } + + // Helper classes to reproduce the issue. + + static class IonAnnotationModule extends SimpleModule { + private static final long serialVersionUID = 3018097049612590165L; + + IonAnnotationModule() { + super("IonAnnotationMod", Version.unknownVersion()); + } + + @Override + public void setupModule(SetupContext context) { + IonAnnotationIntrospector introspector = new ClassNameIonAnnotationIntrospector(); + context.appendAnnotationIntrospector(introspector); + } + } + + static class ClassNameIonAnnotationIntrospector extends IonAnnotationIntrospector { + private static final long serialVersionUID = -5519199636013243472L; + + ClassNameIonAnnotationIntrospector() { + super(true); + } + + @Override + protected TypeIdResolver defaultIdResolver(MapperConfig config, JavaType baseType) { + return new ClassNameIdResolver(baseType, config.getTypeFactory(), config.getPolymorphicTypeValidator()); + } + } + + private static class ClassA { + public int value; + } + + private static class ClassB { + public T content; + } +}