diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 301ead1760..f79f8f0df9 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -34,6 +34,9 @@ Project: jackson-databind (requested by @nathanukey) #4849 Not able to deserialize Enum with default typing after upgrading 2.15.4 -> 2.17.1 (reported by Kornel Zemla) +#4850: Cannot read reference types written with `StdTypeResolverBuilder` + (reported by @isopov) + (fix by Joo-Hyuk K) 2.18.3 (not yet released) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer.java index be81d8beeb..dd0b9c7595 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer.java @@ -257,6 +257,9 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, if (_valueTypeDeserializer == null) { return deserialize(p, ctxt); } - return referenceValue(_valueTypeDeserializer.deserializeTypedFromAny(p, ctxt)); + // [modules-java8#86] Since 2.19.0, Cannot read `Optional`s written with `StdTypeResolverBuilder` + return typeDeserializer.deserializeTypedFromAny(p, ctxt); + // Previously was... + // return referenceValue(_valueTypeDeserializer.deserializeTypedFromAny(p, ctxt)); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ReferenceTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ReferenceTypeSerializer.java index c80bf5afbd..9aa2740e9d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ReferenceTypeSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ReferenceTypeSerializer.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.WritableTypeId; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.introspect.Annotated; @@ -408,17 +410,14 @@ public void serializeWithType(T ref, // (which is what non-polymorphic serialization does too), we will need // to simply delegate call, I think, and NOT try to use it here. - // Otherwise apply type-prefix/suffix, then std serialize: - /* - typeSer.writeTypePrefixForScalar(ref, g); - serialize(ref, g, provider); - typeSer.writeTypeSuffixForScalar(ref, g); - */ + // [modules-java8#86] Since 2.19.0, Bringing back the prefix and suffix writing part + WritableTypeId typeId = typeSer.writeTypePrefix(g, typeSer.typeId(ref, JsonToken.VALUE_STRING)); JsonSerializer ser = _valueSerializer; if (ser == null) { ser = _findCachedSerializer(provider, value.getClass()); } ser.serializeWithType(value, g, provider, typeSer); + typeSer.writeTypeSuffix(g, typeId); } /* diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumSetPolymorphicDeser4214Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumSetPolymorphicDeser4214Test.java index 3c409157d5..f2f2336b13 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumSetPolymorphicDeser4214Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumSetPolymorphicDeser4214Test.java @@ -10,10 +10,8 @@ import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.jsonMapperBuilder; +import static org.junit.jupiter.api.Assertions.*; // For [databind#4214] public class EnumSetPolymorphicDeser4214Test @@ -50,6 +48,6 @@ public void testPolymorphicDeserialization4214() throws Exception String json = mapper.writeValueAsString(enumSetHolder); EnumSetHolder result = mapper.readValue(json, EnumSetHolder.class); assertEquals(result, enumSetHolder); - assertTrue(result.enumSet instanceof EnumSet); + assertInstanceOf(EnumSet.class, result.enumSet); } } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/AtomicReferenceWithStdTypeResolverBuilder4838Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/AtomicReferenceWithStdTypeResolverBuilder4838Test.java new file mode 100644 index 0000000000..696bc81873 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/AtomicReferenceWithStdTypeResolverBuilder4838Test.java @@ -0,0 +1,179 @@ +package com.fasterxml.jackson.databind.deser.jdk; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// Reported via [modules-java8#86] Cannot read `Optional`s written with `StdTypeResolverBuilder` +public class AtomicReferenceWithStdTypeResolverBuilder4838Test + extends DatabindTestUtil +{ + static class Foo { + public AtomicReference value; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Foo foo = (Foo) o; + return Objects.equals(value.get(), foo.value.get()); + } + } + + static class Pojo86 { + public String name; + + public static Pojo86 valueOf(String name) { + Pojo86 pojo = new Pojo86(); + pojo.name = name; + return pojo; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Pojo86 pojo86 = (Pojo86) o; + return Objects.equals(name, pojo86.name); + } + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT) + static abstract class Animal { + public String name; + + protected Animal(String name) { + this.name = name; + } + } + + static class Dog extends Animal { + @JsonCreator + public Dog(@JsonProperty("name") String name) { + super(name); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof Dog) && name.equals(((Dog) obj).name); + } + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) + static abstract class Animal2 { + public String name; + + protected Animal2(String name) { + this.name = name; + } + } + + static class Dog2 extends Animal2 { + @JsonCreator + public Dog2(@JsonProperty("name") String name) { + super(name); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof Dog2) && name.equals(((Dog2) obj).name); + } + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY) + static abstract class Animal3 { + public String name; + + protected Animal3(String name) { + this.name = name; + } + } + + static class Dog3 extends Animal3 { + @JsonCreator + public Dog3(@JsonProperty("name") String name) { + super(name); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof Dog3) && name.equals(((Dog3) obj).name); + } + } + + @Test + public void testRoundTrip() throws Exception { + _test(new AtomicReference<>("MyName"), String.class); + _test(new AtomicReference<>(42), Integer.class); + _test(new AtomicReference<>(Pojo86.valueOf("PojoName")), Pojo86.class); + } + + @Test + public void testPolymorphicWrapperObject() throws Exception { + // Note that default typing set to WRAPPER_OBJECT as well + _test(new AtomicReference<>(new Dog("Buddy")), Animal.class); + } + + @Test + public void testPolymorphicProperty() throws Exception { + _test(new AtomicReference<>(new Dog2("Buttercup")), Animal2.class); + } + + @Test + public void testPolymorphicWrapperArray() throws Exception { + _test(new AtomicReference<>(new Dog3("Buddy")), Animal3.class); + } + + @Test + public void testAtomicReferenceWithMapAndCollection() throws Exception { + // Test AtomicReference with Map + Map sampleMap = new HashMap<>(); + sampleMap.put("key1", 1); + sampleMap.put("key2", 2); + _test(new AtomicReference<>(sampleMap), Map.class); + + // Test AtomicReference with List + List sampleList = Arrays.asList("value1", "value2", "value3"); + _test(new AtomicReference<>(sampleList), List.class); + + // Test AtomicReference with Set + Set sampleSet = new HashSet<>(Arrays.asList(1, 2, 3)); + _test(new AtomicReference<>(sampleSet), Set.class); + + // Test AtomicReference with Queue + Queue sampleQueue = new LinkedList<>(Arrays.asList("q1", "q2", "q3")); + _test(new AtomicReference<>(sampleQueue), Queue.class); + } + + private final ObjectMapper STD_RESOLVER_MAPPER = jsonMapperBuilder() + // this is what's causing failure in later versions..... + .setDefaultTyping( + new StdTypeResolverBuilder() + .init(JsonTypeInfo.Id.CLASS, null) + .inclusion(JsonTypeInfo.As.WRAPPER_OBJECT) + ).build(); + + private void _test(AtomicReference value, Class type) throws Exception { + // Serialize + Foo foo = new Foo<>(); + foo.value = value; + String json = STD_RESOLVER_MAPPER.writeValueAsString(foo); + + // Deserialize + Foo bean = STD_RESOLVER_MAPPER.readValue(json, + STD_RESOLVER_MAPPER.getTypeFactory().constructParametricType(Foo.class, type)); + + // Compare the underlying values of AtomicReference + assertEquals(foo.value.get(), bean.value.get()); + } +}