From 9335323dae9d106702291d4e310e7d1c2a008417 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 9 Jun 2020 16:53:03 -0700 Subject: [PATCH 1/2] Minor test rearrangement; also test (now) obsolete initial tests for #2066 --- .../EmptyArrayAsNullTest.java | 2 +- .../failing/JDKStringLikeTypes2066Test.java | 98 ------------------- 2 files changed, 1 insertion(+), 99 deletions(-) rename src/test/java/com/fasterxml/jackson/databind/{struct => convert}/EmptyArrayAsNullTest.java (99%) delete mode 100644 src/test/java/com/fasterxml/jackson/failing/JDKStringLikeTypes2066Test.java diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/EmptyArrayAsNullTest.java similarity index 99% rename from src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java rename to src/test/java/com/fasterxml/jackson/databind/convert/EmptyArrayAsNullTest.java index 3bad7f9350..ad42a985f8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/struct/EmptyArrayAsNullTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/convert/EmptyArrayAsNullTest.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.databind.struct; +package com.fasterxml.jackson.databind.convert; import java.math.BigDecimal; import java.math.BigInteger; diff --git a/src/test/java/com/fasterxml/jackson/failing/JDKStringLikeTypes2066Test.java b/src/test/java/com/fasterxml/jackson/failing/JDKStringLikeTypes2066Test.java deleted file mode 100644 index 2b44e8299e..0000000000 --- a/src/test/java/com/fasterxml/jackson/failing/JDKStringLikeTypes2066Test.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.fasterxml.jackson.failing; - -import java.util.UUID; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.OptBoolean; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class JDKStringLikeTypes2066Test extends BaseMapTest -{ - static class UUIDWrapper { - public UUID value; - } - - static class StrictUUIDWrapper { - @JsonFormat(lenient=OptBoolean.FALSE) - public UUID value; - } - - /* - /********************************************************** - /* Test methods - /********************************************************** - */ - - // [databind#2066] - public void testUUIDLeniencyDefault() throws Exception - { - final ObjectMapper MAPPER = objectMapper(); - - // By default, empty String OK - assertNull(MAPPER.readValue(quote(""), UUID.class)); - - UUIDWrapper w = MAPPER.readValue("{\"value\":\"\"}", UUIDWrapper.class); - assertNull(w.value); - } - - // [databind#2066] - public void testUUIDLeniencyGlobal() throws Exception - { - final ObjectMapper STRICT_MAPPER = jsonMapperBuilder() - .defaultLeniency(Boolean.FALSE) - .build(); - try { - STRICT_MAPPER.readValue(quote(""), UUID.class); - fail("Should not pass"); - } catch (Exception e) { - _verifyBadUUID(e); - } - - try { - STRICT_MAPPER.readValue("{\"value\":\"\"}", UUIDWrapper.class); - fail("Should not pass"); - } catch (Exception e) { - _verifyBadUUID(e); - } - } - - // [databind#2066] - public void testUUIDLeniencyByType() throws Exception - { - final ObjectMapper STRICT_MAPPER = jsonMapperBuilder().build(); - STRICT_MAPPER.configOverride(UUID.class) - .setFormat(JsonFormat.Value.forLeniency(false)); - - try { - STRICT_MAPPER.readValue(quote(""), UUID.class); - fail("Should not pass"); - } catch (Exception e) { - _verifyBadUUID(e); - } - - try { - STRICT_MAPPER.readValue("{\"value\":\"\"}", UUIDWrapper.class); - fail("Should not pass"); - } catch (Exception e) { - _verifyBadUUID(e); - } - } - - // [databind#2066] - public void testUUIDLeniencyByProperty() throws Exception - { - final ObjectMapper MAPPER = objectMapper(); - try { - MAPPER.readValue("{\"value\":\"\"}", StrictUUIDWrapper.class); - fail("Should not pass"); - } catch (Exception e) { - _verifyBadUUID(e); - } - } - - private void _verifyBadUUID(Exception e) { - verifyException(e, "foobar"); // TODO: real exception message we want - } -} From 831537be2b2d58f4533a3762e5f9f7671d35e499 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 9 Jun 2020 17:23:35 -0700 Subject: [PATCH 2/2] Bit more refactoring to clean up support for `StringBuilder`, coercion tests --- .../deser/std/FromStringDeserializer.java | 63 ++++++++++++++++--- .../deser/std/StdScalarDeserializer.java | 42 ++++++++----- .../convert/CoerceMiscScalarsTest.java | 59 ++++++++++------- 3 files changed, 118 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java index ed6f17018e..a750550b4e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.cfg.CoercionAction; import com.fasterxml.jackson.databind.cfg.CoercionInputShape; import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import com.fasterxml.jackson.databind.type.LogicalType; import com.fasterxml.jackson.databind.util.ClassUtil; /** @@ -69,13 +70,15 @@ public static Class[] types() { TimeZone.class, InetAddress.class, InetSocketAddress.class, + + // Special impl: StringBuilder.class, }; } - + /* /********************************************************** - /* Deserializer implementations + /* Life-cycle /********************************************************** */ @@ -87,7 +90,7 @@ protected FromStringDeserializer(Class vc) { * Factory method for trying to find a deserializer for one of supported * types that have simple from-String serialization. */ - public static Std findDeserializer(Class rawType) + public static FromStringDeserializer findDeserializer(Class rawType) { int kind = 0; if (rawType == File.class) { @@ -115,12 +118,17 @@ public static Std findDeserializer(Class rawType) } else if (rawType == InetSocketAddress.class) { kind = Std.STD_INET_SOCKET_ADDRESS; } else if (rawType == StringBuilder.class) { - kind = Std.STD_STRING_BUILDER; + return new StringBuilderDeserializer(); } else { return null; } return new Std(rawType, kind); } + + @Override // since 2.12 + public LogicalType logicalType() { + return LogicalType.OtherScalar; + } /* /********************************************************** @@ -261,10 +269,11 @@ public static class Std extends FromStringDeserializer public final static int STD_TIME_ZONE = 10; public final static int STD_INET_ADDRESS = 11; public final static int STD_INET_SOCKET_ADDRESS = 12; - public final static int STD_STRING_BUILDER = 13; + // No longer implemented here since 2.12 + // public final static int STD_STRING_BUILDER = 13; protected final int _kind; - + protected Std(Class valueType, int kind) { super(valueType); _kind = kind; @@ -339,8 +348,6 @@ protected Object _deserialize(String value, DeserializationContext ctxt) throws } // host or unbracketed IPv6, without port number return new InetSocketAddress(value, 0); - case STD_STRING_BUILDER: - return new StringBuilder(value); } VersionUtil.throwInternal(); return null; @@ -357,8 +364,6 @@ public Object getEmptyValue(DeserializationContext ctxt) case STD_LOCALE: // As per [databind#1123], Locale too return Locale.ROOT; - case STD_STRING_BUILDER: - return new StringBuilder(); } return super.getEmptyValue(ctxt); } @@ -382,4 +387,42 @@ protected int _firstHyphenOrUnderscore(String str) return -1; } } + + // @since 2.12 to simplify logic a bit: should not use coercions when reading + // String Values + static class StringBuilderDeserializer extends FromStringDeserializer + { + public StringBuilderDeserializer() { + super(StringBuilder.class); + } + + @Override + public LogicalType logicalType() { + return LogicalType.Textual; + } + + @Override + public Object getEmptyValue(DeserializationContext ctxt) + throws JsonMappingException + { + return new StringBuilder(); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException + { + String text = p.getValueAsString(); + if (text != null) { + return _deserialize(text, ctxt); + } + return super.deserialize(p, ctxt); + } + + @Override + protected Object _deserialize(String value, DeserializationContext ctxt) + throws IOException + { + return new StringBuilder(value); + } + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java index 371db0f103..7c21f672d9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java @@ -21,23 +21,12 @@ public abstract class StdScalarDeserializer extends StdDeserializer // since 2.5 protected StdScalarDeserializer(StdScalarDeserializer src) { super(src); } - - @Override - public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { - return typeDeserializer.deserializeTypedFromScalar(p, ctxt); - } - /** - * Overridden to simply call deserialize() method that does not take value - * to update, since scalar values are usually non-mergeable. + /* + /********************************************************************** + /* Overridden accessors + /********************************************************************** */ - @Override // since 2.9 - public T deserialize(JsonParser p, DeserializationContext ctxt, T intoValue) throws IOException { - // 25-Oct-2016, tatu: And if attempt is made, see if we are to complain... - ctxt.handleBadMerge(this); - // if that does not report an exception we can just delegate - return deserialize(p, ctxt); - } @Override // since 2.12 public LogicalType logicalType() { @@ -65,4 +54,27 @@ public AccessPattern getNullAccessPattern() { public AccessPattern getEmptyAccessPattern() { return AccessPattern.CONSTANT; } + + /* + /********************************************************************** + /* Default deserialization method impls + /********************************************************************** + */ + + @Override + public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { + return typeDeserializer.deserializeTypedFromScalar(p, ctxt); + } + + /** + * Overridden to simply call deserialize() method that does not take value + * to update, since scalar values are usually non-mergeable. + */ + @Override // since 2.9 + public T deserialize(JsonParser p, DeserializationContext ctxt, T intoValue) throws IOException { + // 25-Oct-2016, tatu: And if attempt is made, see if we are to complain... + ctxt.handleBadMerge(this); + // if that does not report an exception we can just delegate + return deserialize(p, ctxt); + } } diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/CoerceMiscScalarsTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceMiscScalarsTest.java index 6f9d6726e3..06b9f98df8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/convert/CoerceMiscScalarsTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/convert/CoerceMiscScalarsTest.java @@ -83,12 +83,6 @@ public void testScalarDefaultsFromEmpty() throws Exception _testScalarEmptyToNull(DEFAULT_MAPPER, TimeZone.class); _testScalarEmptyToNull(DEFAULT_MAPPER, InetAddress.class); _testScalarEmptyToNull(DEFAULT_MAPPER, InetSocketAddress.class); - - { - StringBuilder result = DEFAULT_MAPPER.readValue(JSON_EMPTY, StringBuilder.class); - assertNotNull(result); - assertEquals(0, result.length()); - } } /* @@ -113,8 +107,6 @@ public void testScalarEmptyToNull() throws Exception _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, TimeZone.class); _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, InetAddress.class); _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, InetSocketAddress.class); - - _testScalarEmptyToNull(MAPPER_EMPTY_TO_NULL, StringBuilder.class); } public void testScalarEmptyToEmpty() throws Exception @@ -140,12 +132,6 @@ public void testScalarEmptyToEmpty() throws Exception _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, TimeZone.class); _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, InetAddress.class); _testScalarEmptyToNull(MAPPER_EMPTY_TO_EMPTY, InetSocketAddress.class); - - { - StringBuilder result = MAPPER_EMPTY_TO_EMPTY.readValue(JSON_EMPTY, StringBuilder.class); - assertNotNull(result); - assertEquals(0, result.length()); - } } public void testScalarEmptyToTryConvert() throws Exception @@ -171,12 +157,6 @@ public void testScalarEmptyToTryConvert() throws Exception _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, TimeZone.class); _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, InetAddress.class); _testScalarEmptyToNull(MAPPER_EMPTY_TO_TRY_CONVERT, InetSocketAddress.class); - - { - StringBuilder result = MAPPER_EMPTY_TO_TRY_CONVERT.readValue(JSON_EMPTY, StringBuilder.class); - assertNotNull(result); - assertEquals(0, result.length()); - } } /* @@ -188,6 +168,43 @@ public void testScalarEmptyToTryConvert() throws Exception public void testScalarsFailFromEmpty() throws Exception { _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, UUID.class); + + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, File.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, URL.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, URI.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Class.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, JavaType.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Currency.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Pattern.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Locale.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, Charset.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, TimeZone.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, InetAddress.class); + _verifyScalarToFail(MAPPER_EMPTY_TO_FAIL, InetSocketAddress.class); + } + + /* + /******************************************************** + /* Test methods, special type(s) + /******************************************************** + */ + + // StringBuilder is its own special type, since it naturally maps + // from String values, hence separate testing + public void testStringBuilderDeser() throws Exception + { + // should result in an "empty" StringBuilder for all valid settings + _checkEmptyStringBuilder(DEFAULT_MAPPER.readValue(JSON_EMPTY, StringBuilder.class)); + _checkEmptyStringBuilder(MAPPER_EMPTY_TO_EMPTY.readValue(JSON_EMPTY, StringBuilder.class)); + _checkEmptyStringBuilder(MAPPER_EMPTY_TO_TRY_CONVERT.readValue(JSON_EMPTY, StringBuilder.class)); + _checkEmptyStringBuilder(MAPPER_EMPTY_TO_NULL.readValue(JSON_EMPTY, StringBuilder.class)); + // and even alleged failure should not result in that since it's not coercion + _checkEmptyStringBuilder(MAPPER_EMPTY_TO_FAIL.readValue(JSON_EMPTY, StringBuilder.class)); + } + + private void _checkEmptyStringBuilder(StringBuilder sb) { + assertNotNull(sb); + assertEquals(0, sb.length()); } /* @@ -195,7 +212,7 @@ public void testScalarsFailFromEmpty() throws Exception /* Second-level test helper methods /******************************************************** */ - + private void _testScalarEmptyToNull(ObjectMapper mapper, Class target) throws Exception { assertNull(mapper.readerFor(target).readValue(JSON_EMPTY));