From 0aa62bdd206482258b44dd536c639da8b219c51c Mon Sep 17 00:00:00 2001 From: joohyukkim Date: Sat, 7 Dec 2024 15:26:32 +0900 Subject: [PATCH 1/5] .. --- .../datatype/jdk8/OptionalDeserializer.java | 39 +++++++++ .../datatype/jdk8/OptionalSerializer.java | 37 +++++++++ .../jdk8/OptionalWithTypeResolver86Test.java | 83 +++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java diff --git a/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalDeserializer.java b/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalDeserializer.java index 08a6c8a5..ad67a190 100644 --- a/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalDeserializer.java +++ b/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalDeserializer.java @@ -1,7 +1,10 @@ package com.fasterxml.jackson.datatype.jdk8; +import java.io.IOException; import java.util.Optional; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.ValueInstantiator; import com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer; @@ -104,4 +107,40 @@ public Optional updateReference(Optional reference, Object contents) { // Default ought to be fine: // public Boolean supportsUpdate(DeserializationConfig config) { } + /* + /********************************************************** + /* Deserialization + /********************************************************** + */ + + /** + * [modules-java8#86] Cannot read {@link java.util.Optional}'s written with + * {@link com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder}. + * This override implementation was removed in 2.9, but restored and + * modified in 2.19 due to [modules-java8#86]. + * + * @since 2.19 + */ + @Override + public Optional deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) + throws IOException + { + final JsonToken t = p.getCurrentToken(); + if (t == JsonToken.VALUE_NULL) { + return getNullValue(ctxt); + } + // 03-Nov-2013, tatu: This gets rather tricky with "natural" types + // (String, Integer, Boolean), which do NOT include type information. + // These might actually be handled ok except that nominal type here + // is `Optional`, so special handling is not invoked; instead, need + // to do a work-around here. + // 22-Oct-2015, tatu: Most likely this is actually wrong, result of incorrewct + // serialization (up to 2.6, was omitting necessary type info after all); + // but safest to leave in place for now + if (t != null && t.isScalarValue()) { + return deserialize(p, ctxt); + } + return (Optional) typeDeserializer.deserializeTypedFromAny(p, ctxt); + } + } diff --git a/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalSerializer.java b/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalSerializer.java index 77ec0edf..d6a248e7 100644 --- a/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalSerializer.java +++ b/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalSerializer.java @@ -1,7 +1,11 @@ package com.fasterxml.jackson.datatype.jdk8; +import java.io.IOException; import java.util.Optional; +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.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.std.ReferenceTypeSerializer; @@ -71,4 +75,37 @@ protected Object _getReferenced(Optional value) { protected Object _getReferencedIfPresent(Optional value) { return value.isPresent() ? value.get() : null; } + + /* + /********************************************************** + /* Serialization + /********************************************************** + */ + + /** + * [modules-java8#86] Cannot read {@link java.util.Optional}'s written with + * {@link com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder}. + * This override implementation was removed in 2.9, but restored and + * modified in 2.19 due to [modules-java8#86]. + * + * @since 2.19 + */ + @Override + public void serializeWithType(Optional ref, JsonGenerator g, + SerializerProvider provider, TypeSerializer typeSer) + throws IOException + { + if (!ref.isPresent()) { + // [datatype-jdk8#20]: can not write `null` if unwrapped + if (_unwrapper == null) { + provider.defaultSerializeNull(g); + } + return; + } + WritableTypeId typeId = typeSer.writeTypePrefix(g, + typeSer.typeId(ref, JsonToken.VALUE_STRING)); + serialize(ref, g, provider); + typeSer.writeTypeSuffix(g, typeId); + } + } diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java new file mode 100644 index 00000000..848293b2 --- /dev/null +++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java @@ -0,0 +1,83 @@ +package com.fasterxml.jackson.datatype.jdk8; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder; +import org.junit.Test; + +import java.util.Objects; +import java.util.Optional; + +// [modules-java8#86] Cannot read Optionals written with StdTypeResolverBuilder +public class OptionalWithTypeResolver86Test + extends ModuleTestBase +{ + + public static class Foo { + public Optional value; + } + + public static class Pojo86 { + public String name; + + // with static method + 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); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + } + + @Test + public void test() throws Exception { + _testOptionalWith(Optional.of("MyName"), String.class, "MyName"); + _testOptionalWith(Optional.of(42), Integer.class, 42); + _testOptionalWith(Optional.of(Pojo86.valueOf("PojoName")), Pojo86.class, Pojo86.valueOf("PojoName")); + } + + private void _testOptionalWith(Optional value, Class type, T expectedValue) + throws Exception + { + ObjectMapper mapper = configureObjectMapper(); + + // Serialize + Foo foo = new Foo<>(); + foo.value = value; + String json = mapper.writeValueAsString(foo); + String expectedJSON = a2q(String.format( + "{'%s':{'value':{'%s':%s}}}", + Foo.class.getName(), + Optional.class.getName(), + mapper.writeValueAsString(expectedValue) + )); + assertEquals(expectedJSON, json); + + // Deserialize + Foo bean = mapper.readValue(json, mapper.getTypeFactory().constructParametricType(Foo.class, type)); + assertEquals(value, bean.value); + } + + private ObjectMapper configureObjectMapper() { + ObjectMapper mapper = mapperWithModule(); + mapper.setDefaultTyping( + new StdTypeResolverBuilder() + .init(JsonTypeInfo.Id.CLASS, null) + .inclusion(JsonTypeInfo.As.WRAPPER_OBJECT) + ); + return mapper; + } + +} From dce9ac412e668b45c5a1fb4c5c95babf43edb94b Mon Sep 17 00:00:00 2001 From: joohyukkim Date: Sat, 7 Dec 2024 15:31:24 +0900 Subject: [PATCH 2/5] Fix comment --- .../jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java index 848293b2..d976ebb2 100644 --- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java +++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java @@ -8,7 +8,7 @@ import java.util.Objects; import java.util.Optional; -// [modules-java8#86] Cannot read Optionals written with StdTypeResolverBuilder +// [modules-java8#86] Cannot read `Optional`s written with `StdTypeResolverBuilder` public class OptionalWithTypeResolver86Test extends ModuleTestBase { From 156ac10eccad8e4cfe1f044b1dde97b0ac214d19 Mon Sep 17 00:00:00 2001 From: joohyukkim Date: Sat, 7 Dec 2024 15:34:32 +0900 Subject: [PATCH 3/5] chore : organize code --- .../jdk8/OptionalWithTypeResolver86Test.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java index d976ebb2..cf840640 100644 --- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java +++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java @@ -1,12 +1,11 @@ package com.fasterxml.jackson.datatype.jdk8; +import java.util.Objects; +import java.util.Optional; + import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder; -import org.junit.Test; - -import java.util.Objects; -import java.util.Optional; // [modules-java8#86] Cannot read `Optional`s written with `StdTypeResolverBuilder` public class OptionalWithTypeResolver86Test @@ -41,11 +40,13 @@ public int hashCode() { } } - @Test - public void test() throws Exception { + public void testRoundTrip() + throws Exception + { _testOptionalWith(Optional.of("MyName"), String.class, "MyName"); _testOptionalWith(Optional.of(42), Integer.class, 42); - _testOptionalWith(Optional.of(Pojo86.valueOf("PojoName")), Pojo86.class, Pojo86.valueOf("PojoName")); + _testOptionalWith(Optional.of(Pojo86.valueOf("PojoName")), + Pojo86.class, Pojo86.valueOf("PojoName")); } private void _testOptionalWith(Optional value, Class type, T expectedValue) @@ -66,7 +67,8 @@ private void _testOptionalWith(Optional value, Class type, T expectedV assertEquals(expectedJSON, json); // Deserialize - Foo bean = mapper.readValue(json, mapper.getTypeFactory().constructParametricType(Foo.class, type)); + Foo bean = mapper.readValue(json, + mapper.getTypeFactory().constructParametricType(Foo.class, type)); assertEquals(value, bean.value); } From 1943fce7ef6d51cf4aaf6a17949c80922314af35 Mon Sep 17 00:00:00 2001 From: joohyukkim Date: Sun, 8 Dec 2024 18:06:39 +0900 Subject: [PATCH 4/5] Remove module implementations --- .../datatype/jdk8/OptionalDeserializer.java | 39 ------------------- .../datatype/jdk8/OptionalSerializer.java | 37 ------------------ 2 files changed, 76 deletions(-) diff --git a/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalDeserializer.java b/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalDeserializer.java index ad67a190..08a6c8a5 100644 --- a/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalDeserializer.java +++ b/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalDeserializer.java @@ -1,10 +1,7 @@ package com.fasterxml.jackson.datatype.jdk8; -import java.io.IOException; import java.util.Optional; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.ValueInstantiator; import com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer; @@ -107,40 +104,4 @@ public Optional updateReference(Optional reference, Object contents) { // Default ought to be fine: // public Boolean supportsUpdate(DeserializationConfig config) { } - /* - /********************************************************** - /* Deserialization - /********************************************************** - */ - - /** - * [modules-java8#86] Cannot read {@link java.util.Optional}'s written with - * {@link com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder}. - * This override implementation was removed in 2.9, but restored and - * modified in 2.19 due to [modules-java8#86]. - * - * @since 2.19 - */ - @Override - public Optional deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) - throws IOException - { - final JsonToken t = p.getCurrentToken(); - if (t == JsonToken.VALUE_NULL) { - return getNullValue(ctxt); - } - // 03-Nov-2013, tatu: This gets rather tricky with "natural" types - // (String, Integer, Boolean), which do NOT include type information. - // These might actually be handled ok except that nominal type here - // is `Optional`, so special handling is not invoked; instead, need - // to do a work-around here. - // 22-Oct-2015, tatu: Most likely this is actually wrong, result of incorrewct - // serialization (up to 2.6, was omitting necessary type info after all); - // but safest to leave in place for now - if (t != null && t.isScalarValue()) { - return deserialize(p, ctxt); - } - return (Optional) typeDeserializer.deserializeTypedFromAny(p, ctxt); - } - } diff --git a/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalSerializer.java b/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalSerializer.java index d6a248e7..77ec0edf 100644 --- a/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalSerializer.java +++ b/datatypes/src/main/java/com/fasterxml/jackson/datatype/jdk8/OptionalSerializer.java @@ -1,11 +1,7 @@ package com.fasterxml.jackson.datatype.jdk8; -import java.io.IOException; import java.util.Optional; -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.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.std.ReferenceTypeSerializer; @@ -75,37 +71,4 @@ protected Object _getReferenced(Optional value) { protected Object _getReferencedIfPresent(Optional value) { return value.isPresent() ? value.get() : null; } - - /* - /********************************************************** - /* Serialization - /********************************************************** - */ - - /** - * [modules-java8#86] Cannot read {@link java.util.Optional}'s written with - * {@link com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder}. - * This override implementation was removed in 2.9, but restored and - * modified in 2.19 due to [modules-java8#86]. - * - * @since 2.19 - */ - @Override - public void serializeWithType(Optional ref, JsonGenerator g, - SerializerProvider provider, TypeSerializer typeSer) - throws IOException - { - if (!ref.isPresent()) { - // [datatype-jdk8#20]: can not write `null` if unwrapped - if (_unwrapper == null) { - provider.defaultSerializeNull(g); - } - return; - } - WritableTypeId typeId = typeSer.writeTypePrefix(g, - typeSer.typeId(ref, JsonToken.VALUE_STRING)); - serialize(ref, g, provider); - typeSer.writeTypeSuffix(g, typeId); - } - } From d5513d70505c055f7c2b251724c6afa92790cdec Mon Sep 17 00:00:00 2001 From: joohyukkim Date: Sun, 8 Dec 2024 18:09:03 +0900 Subject: [PATCH 5/5] Add more test --- .../jdk8/OptionalWithTypeResolver86Test.java | 68 ++++++++++++++++--- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java index cf840640..1b256d33 100644 --- a/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java +++ b/datatypes/src/test/java/com/fasterxml/jackson/datatype/jdk8/OptionalWithTypeResolver86Test.java @@ -3,17 +3,28 @@ import java.util.Objects; import java.util.Optional; +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; // [modules-java8#86] Cannot read `Optional`s written with `StdTypeResolverBuilder` +// public class OptionalWithTypeResolver86Test extends ModuleTestBase { public static class Foo { public Optional 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, foo.value); + } } public static class Pojo86 { @@ -34,9 +45,28 @@ public boolean equals(Object o) { return Objects.equals(name, pojo86.name); } + } + + // Base class for polymorphic types + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT) + public static abstract class Animal { + public String name; + + protected Animal(String name) { + this.name = name; + } + } + + // Subclass: Dog + public static class Dog extends Animal { + @JsonCreator + public Dog(@JsonProperty("name") String name) { + super(name); + } + @Override - public int hashCode() { - return Objects.hashCode(name); + public boolean equals(Object obj) { + return (obj instanceof Dog) && name.equals(((Dog) obj).name); } } @@ -49,6 +79,30 @@ public void testRoundTrip() Pojo86.class, Pojo86.valueOf("PojoName")); } + public void testRoundTripPolymorphic() + throws Exception + { + _testOptionalPolymorphicWith( + Optional.of(new Dog("Buddy")) + ); + } + + private void _testOptionalPolymorphicWith(Optional value) + throws Exception + { + ObjectMapper mapper = configureObjectMapper(); + + // Serialize + Foo foo = new Foo<>(); + foo.value = value; + String json = mapper.writeValueAsString(foo); + + // Deserialize + Foo bean = mapper.readValue(json, + mapper.getTypeFactory().constructParametricType(Foo.class, Animal.class)); + assertEquals(foo, bean); // Compare Foo objects directly + } + private void _testOptionalWith(Optional value, Class type, T expectedValue) throws Exception { @@ -58,21 +112,15 @@ private void _testOptionalWith(Optional value, Class type, T expectedV Foo foo = new Foo<>(); foo.value = value; String json = mapper.writeValueAsString(foo); - String expectedJSON = a2q(String.format( - "{'%s':{'value':{'%s':%s}}}", - Foo.class.getName(), - Optional.class.getName(), - mapper.writeValueAsString(expectedValue) - )); - assertEquals(expectedJSON, json); // Deserialize Foo bean = mapper.readValue(json, mapper.getTypeFactory().constructParametricType(Foo.class, type)); assertEquals(value, bean.value); + assertEquals(expectedValue, bean.value.get()); } - private ObjectMapper configureObjectMapper() { + private ObjectMapper configureObjectMapper(){ ObjectMapper mapper = mapperWithModule(); mapper.setDefaultTyping( new StdTypeResolverBuilder()