From 498d9663e276526e4ba7762167fa01d2d0625ecd Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 15 Oct 2023 21:11:23 +0900 Subject: [PATCH 1/5] Fix for 3569 --- .../jackson/databind/ObjectMapper.java | 4 +- .../AsWrapperArrayEnumDeser3569Test.java | 62 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 0ce19c8689..516810212e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -353,7 +353,9 @@ public boolean useForType(JavaType t) t = t.getReferencedType(); } // [databind#88] Should not apply to JSON tree models: - return !t.isFinal() && !TreeNode.class.isAssignableFrom(t.getRawClass()); + return (!t.isFinal() && !TreeNode.class.isAssignableFrom(t.getRawClass())) + // [databind#3569] Allow use of default typing for Enums + || t.isEnumType(); case EVERYTHING: // So, excluding primitives (handled earlier) and "Natural types" (handled // before this method is called), applied to everything diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java new file mode 100644 index 0000000000..0766ff98e1 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java @@ -0,0 +1,62 @@ +package com.fasterxml.jackson.databind.jsontype.deftyping; + +import static com.fasterxml.jackson.databind.BaseMapTest.jsonMapperBuilder; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.DefaultBaseTypeLimitingValidator; +import org.junit.Test; + +/** + * [databind#3569]: Unable to deserialize enum object with default-typed + * {@link com.fasterxml.jackson.annotation.JsonTypeInfo.As#WRAPPER_ARRAY} and {@link JsonCreator} together. + */ +public class AsWrapperArrayEnumDeser3569Test +{ + static class Foo { + public T item; + } + + enum Bar { + ENABLED, + DISABLED, + HIDDEN; + + @JsonCreator + public static Bar fromValue(String value) { + String upperVal = value.toUpperCase(); + for (Bar enumValue : Bar.values()) { + if (enumValue.name().equals(upperVal)) { + return enumValue; + } + } + throw new IllegalArgumentException("Bad input [" + value + "]"); + } + } + + @Test + public void testEnumAsWrapperArrayWithCreator() throws JsonProcessingException { + ObjectMapper objectMapper = jsonMapperBuilder() + .activateDefaultTyping( + new DefaultBaseTypeLimitingValidator(), + ObjectMapper.DefaultTyping.NON_FINAL, + JsonTypeInfo.As.WRAPPER_ARRAY) + .build(); + + Foo expected = new Foo<>(); + expected.item = Bar.ENABLED; + + // First, serialize + String serialized = objectMapper.writeValueAsString(expected); + + // Then, deserialize with TypeReference + assertNotNull(objectMapper.readValue(serialized, new TypeReference>() {})); + // And, also try as described in [databind#3569] + JavaType javaType = objectMapper.getTypeFactory().constructParametricType(Foo.class, new Class[]{Bar.class}); + assertNotNull(objectMapper.readValue(serialized, javaType)); + } +} From a5ff9dd1702247066a43db78a101d0ed070ff25a Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 15 Oct 2023 21:17:29 +0900 Subject: [PATCH 2/5] Fix JavaDoc and etc... --- .../java/com/fasterxml/jackson/databind/ObjectMapper.java | 2 +- .../java/com/fasterxml/jackson/databind/BaseMapTest.java | 2 +- .../jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 516810212e..919fccb7f6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -354,7 +354,7 @@ public boolean useForType(JavaType t) } // [databind#88] Should not apply to JSON tree models: return (!t.isFinal() && !TreeNode.class.isAssignableFrom(t.getRawClass())) - // [databind#3569] Allow use of default typing for Enums + // [databind#3569] Allow use of default typing for Enums -- since 2.15.4 || t.isEnumType(); case EVERYTHING: // So, excluding primitives (handled earlier) and "Natural types" (handled diff --git a/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java b/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java index 3e0f006e3e..2e49fa3ac9 100644 --- a/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java @@ -244,7 +244,7 @@ protected static ObjectMapper newJsonMapper() { } // @since 2.10 - protected static JsonMapper.Builder jsonMapperBuilder() { + public static JsonMapper.Builder jsonMapperBuilder() { return JsonMapper.builder(); } diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java index 0766ff98e1..ba7531a743 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java @@ -14,6 +14,8 @@ /** * [databind#3569]: Unable to deserialize enum object with default-typed * {@link com.fasterxml.jackson.annotation.JsonTypeInfo.As#WRAPPER_ARRAY} and {@link JsonCreator} together. + * + * @since 2.15.4 */ public class AsWrapperArrayEnumDeser3569Test { @@ -22,9 +24,7 @@ static class Foo { } enum Bar { - ENABLED, - DISABLED, - HIDDEN; + ENABLED, DISABLED, HIDDEN; @JsonCreator public static Bar fromValue(String value) { From 614e30b00bf5a1d5746f0a9e0bd246982b6d319e Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Tue, 17 Oct 2023 08:38:47 +0900 Subject: [PATCH 3/5] Clean up --- .../jackson/databind/ObjectMapper.java | 4 +- .../AsWrapperArrayEnumDeser3569Test.java | 62 ------------------- .../deftyping/TestDefaultForEnums.java | 53 ++++++++++++++++ 3 files changed, 54 insertions(+), 65 deletions(-) delete mode 100644 src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 919fccb7f6..0ce19c8689 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -353,9 +353,7 @@ public boolean useForType(JavaType t) t = t.getReferencedType(); } // [databind#88] Should not apply to JSON tree models: - return (!t.isFinal() && !TreeNode.class.isAssignableFrom(t.getRawClass())) - // [databind#3569] Allow use of default typing for Enums -- since 2.15.4 - || t.isEnumType(); + return !t.isFinal() && !TreeNode.class.isAssignableFrom(t.getRawClass()); case EVERYTHING: // So, excluding primitives (handled earlier) and "Natural types" (handled // before this method is called), applied to everything diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java deleted file mode 100644 index ba7531a743..0000000000 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/AsWrapperArrayEnumDeser3569Test.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.fasterxml.jackson.databind.jsontype.deftyping; - -import static com.fasterxml.jackson.databind.BaseMapTest.jsonMapperBuilder; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.jsontype.DefaultBaseTypeLimitingValidator; -import org.junit.Test; - -/** - * [databind#3569]: Unable to deserialize enum object with default-typed - * {@link com.fasterxml.jackson.annotation.JsonTypeInfo.As#WRAPPER_ARRAY} and {@link JsonCreator} together. - * - * @since 2.15.4 - */ -public class AsWrapperArrayEnumDeser3569Test -{ - static class Foo { - public T item; - } - - enum Bar { - ENABLED, DISABLED, HIDDEN; - - @JsonCreator - public static Bar fromValue(String value) { - String upperVal = value.toUpperCase(); - for (Bar enumValue : Bar.values()) { - if (enumValue.name().equals(upperVal)) { - return enumValue; - } - } - throw new IllegalArgumentException("Bad input [" + value + "]"); - } - } - - @Test - public void testEnumAsWrapperArrayWithCreator() throws JsonProcessingException { - ObjectMapper objectMapper = jsonMapperBuilder() - .activateDefaultTyping( - new DefaultBaseTypeLimitingValidator(), - ObjectMapper.DefaultTyping.NON_FINAL, - JsonTypeInfo.As.WRAPPER_ARRAY) - .build(); - - Foo expected = new Foo<>(); - expected.item = Bar.ENABLED; - - // First, serialize - String serialized = objectMapper.writeValueAsString(expected); - - // Then, deserialize with TypeReference - assertNotNull(objectMapper.readValue(serialized, new TypeReference>() {})); - // And, also try as described in [databind#3569] - JavaType javaType = objectMapper.getTypeFactory().constructParametricType(Foo.class, new Class[]{Bar.class}); - assertNotNull(objectMapper.readValue(serialized, javaType)); - } -} diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java index dd63a891dd..046137f9d8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java @@ -1,11 +1,19 @@ package com.fasterxml.jackson.databind.jsontype.deftyping; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.jsontype.DefaultBaseTypeLimitingValidator; import java.util.concurrent.TimeUnit; import com.fasterxml.jackson.databind.BaseMapTest; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.testutil.NoCheckSubTypeValidator; +import org.junit.Test; public class TestDefaultForEnums extends BaseMapTest @@ -26,6 +34,25 @@ protected static class TimeUnitBean { public TimeUnit timeUnit; } + static class Foo3569 { + public T item; + } + + enum Bar3569 { + ENABLED, DISABLED, HIDDEN; + + @JsonCreator + public static Bar3569 fromValue(String value) { + String upperVal = value.toUpperCase(); + for (Bar3569 enumValue : Bar3569.values()) { + if (enumValue.name().equals(upperVal)) { + return enumValue; + } + } + throw new IllegalArgumentException("Bad input [" + value + "]"); + } + } + /* /********************************************************** /* Test methods @@ -78,4 +105,30 @@ public void testSimpleEnumsAsField() throws Exception EnumHolder holder = m.readValue(json, EnumHolder.class); assertSame(TestEnum.B, holder.value); } + + /* + * [databind#3569]: Unable to deserialize enum object with default-typed + * {@link com.fasterxml.jackson.annotation.JsonTypeInfo.As#WRAPPER_ARRAY} and {@link JsonCreator} together, + */ + public void testEnumAsWrapperArrayWithCreator() throws JsonProcessingException + { + ObjectMapper objectMapper = jsonMapperBuilder() + .activateDefaultTyping( + new DefaultBaseTypeLimitingValidator(), + ObjectMapper.DefaultTyping.EVERYTHING, + JsonTypeInfo.As.WRAPPER_ARRAY) + .build(); + + Foo3569 expected = new Foo3569<>(); + expected.item = Bar3569.ENABLED; + + // First, serialize + String serialized = objectMapper.writeValueAsString(expected); + + // Then, deserialize with TypeReference + assertNotNull(objectMapper.readValue(serialized, new TypeReference>() {})); + // And, also try as described in [databind#3569] + JavaType javaType = objectMapper.getTypeFactory().constructParametricType(Foo3569.class, new Class[]{Bar3569.class}); + assertNotNull(objectMapper.readValue(serialized, javaType)); + } } From fb35da1062d0077633ae5329e04b7fb9c8820d16 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Tue, 17 Oct 2023 08:39:04 +0900 Subject: [PATCH 4/5] Update BaseMapTest.java --- src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java b/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java index 2e49fa3ac9..3e0f006e3e 100644 --- a/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java @@ -244,7 +244,7 @@ protected static ObjectMapper newJsonMapper() { } // @since 2.10 - public static JsonMapper.Builder jsonMapperBuilder() { + protected static JsonMapper.Builder jsonMapperBuilder() { return JsonMapper.builder(); } From 5ec00ec7f3ea2f1027d8a87061d18cc6ff43a0d7 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Wed, 18 Oct 2023 00:22:02 +0900 Subject: [PATCH 5/5] Add new feature --- .../jackson/databind/ObjectMapper.java | 24 +++++++++++++++++++ .../deftyping/TestDefaultForEnums.java | 8 +++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 0ce19c8689..12941d072f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -188,6 +188,16 @@ public enum DefaultTyping { */ NON_FINAL, + /** + * Enables default typing for non-final types as {@link #NON_FINAL}, + * but also includes Enums. + * Designed to allow default typing of Enums without resorting to + * {@link #EVERYTHING}, which has security implications. + *

+ * @since 2.16 + */ + NON_FINAL_AND_ENUMS, + /** * Value that means that default typing will be used for * all types, with exception of small number of @@ -354,6 +364,20 @@ public boolean useForType(JavaType t) } // [databind#88] Should not apply to JSON tree models: return !t.isFinal() && !TreeNode.class.isAssignableFrom(t.getRawClass()); + + case NON_FINAL_AND_ENUMS: // since 2.16 + while (t.isArrayType()) { + t = t.getContentType(); + } + // 19-Apr-2016, tatu: ReferenceType like Optional also requires similar handling: + while (t.isReferenceType()) { + t = t.getReferencedType(); + } + // [databind#88] Should not apply to JSON tree models: + return (!t.isFinal() && !TreeNode.class.isAssignableFrom(t.getRawClass())) + // [databind#3569] Allow use of default typing for Enums + || t.isEnumType(); + case EVERYTHING: // So, excluding primitives (handled earlier) and "Natural types" (handled // before this method is called), applied to everything diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java index 046137f9d8..4cd884d7c7 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForEnums.java @@ -1,6 +1,5 @@ package com.fasterxml.jackson.databind.jsontype.deftyping; -import static org.junit.jupiter.api.Assertions.assertNotNull; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.core.JsonProcessingException; @@ -13,7 +12,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.testutil.NoCheckSubTypeValidator; -import org.junit.Test; public class TestDefaultForEnums extends BaseMapTest @@ -106,16 +104,18 @@ public void testSimpleEnumsAsField() throws Exception assertSame(TestEnum.B, holder.value); } - /* + /** * [databind#3569]: Unable to deserialize enum object with default-typed * {@link com.fasterxml.jackson.annotation.JsonTypeInfo.As#WRAPPER_ARRAY} and {@link JsonCreator} together, + * + * @since 2.16 */ public void testEnumAsWrapperArrayWithCreator() throws JsonProcessingException { ObjectMapper objectMapper = jsonMapperBuilder() .activateDefaultTyping( new DefaultBaseTypeLimitingValidator(), - ObjectMapper.DefaultTyping.EVERYTHING, + ObjectMapper.DefaultTyping.NON_FINAL_AND_ENUMS, JsonTypeInfo.As.WRAPPER_ARRAY) .build();