From 7ba9ac5b87a9d6ac0d2815158ecbeb315ad4dcdc Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 31 May 2020 11:25:20 -0700 Subject: [PATCH] Yet more refactoring to figure out coercion for empty String --- .../databind/deser/BeanDeserializerBase.java | 2 +- .../deser/std/MapEntryDeserializer.java | 7 +- .../databind/deser/std/StdDeserializer.java | 199 +++++++++--------- 3 files changed, 99 insertions(+), 109 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java index 5ebb4a9f19..54447b2400 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -1529,7 +1529,7 @@ public Object deserializeFromEmbedded(JsonParser p, DeserializationContext ctxt) /** * @since 2.9 */ - private final JsonDeserializer _delegateDeserializer() { + protected final JsonDeserializer _delegateDeserializer() { JsonDeserializer deser = _delegateDeserializer; if (deser == null) { deser = _arrayDelegateDeserializer; diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java index 0ad154cd19..6a76f04283 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java @@ -175,13 +175,10 @@ public Map.Entry deserialize(JsonParser p, DeserializationContext { // Ok: must point to START_OBJECT, FIELD_NAME or END_OBJECT JsonToken t = p.currentToken(); - if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) { - // String may be ok however: - // slightly redundant (since String was passed above), but - return _deserializeFromEmpty(p, ctxt); - } if (t == JsonToken.START_OBJECT) { t = p.nextToken(); + } else if (t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) { + return _deserializeFromEmpty(p, ctxt); } if (t != JsonToken.FIELD_NAME) { if (t == JsonToken.END_OBJECT) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java index 56aee3ea73..e151e43a05 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java @@ -142,7 +142,7 @@ protected boolean isDefaultDeserializer(JsonDeserializer deserializer) { protected boolean isDefaultKeyDeserializer(KeyDeserializer keyDeser) { return ClassUtil.isJacksonStdImpl(keyDeser); } - + /* /********************************************************** /* Partial JsonDeserializer implementation @@ -160,6 +160,101 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, return typeDeserializer.deserializeTypedFromAny(p, ctxt); } + /* + /********************************************************** + /* High-level handling of secondary input shapes (with + /* possible coercion) + /********************************************************** + */ + + /** + * Helper method that allows easy support for array-related {@link DeserializationFeature}s + * `ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT` and `UNWRAP_SINGLE_VALUE_ARRAYS`: checks for either + * empty array, or single-value array-wrapped value (respectively), and either reports + * an exception (if no match, or feature(s) not enabled), or returns appropriate + * result value. + *

+ * This method should NOT be called if Array representation is explicitly supported + * for type: it should only be called in case it is otherwise unrecognized. + *

+ * NOTE: in case of unwrapped single element, will handle actual decoding + * by calling {@link #_deserializeWrappedValue}, which by default calls + * {@link #deserialize(JsonParser, DeserializationContext)}. + * + * @since 2.9 + */ + protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException + { + JsonToken t; + if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) { + t = p.nextToken(); + if (t == JsonToken.END_ARRAY) { + if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { + return getNullValue(ctxt); + } + } + if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + final T parsed = deserialize(p, ctxt); + if (p.nextToken() != JsonToken.END_ARRAY) { + handleMissingEndArrayForSingle(p, ctxt); + } + return parsed; + } + } else { + t = p.currentToken(); + } + @SuppressWarnings("unchecked") + T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, null); + return result; + } + + /** + * Helper method that may be used to support fallback for Empty String / Empty Array + * non-standard representations; usually for things serialized as JSON Objects. + * + * @since 2.5 + */ + @SuppressWarnings("unchecked") + protected T _deserializeFromEmpty(JsonParser p, DeserializationContext ctxt) + throws IOException + { + JsonToken t = p.currentToken(); + if (t == JsonToken.START_ARRAY) { + if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { + t = p.nextToken(); + if (t == JsonToken.END_ARRAY) { + return null; + } + return (T) ctxt.handleUnexpectedToken(handledType(), p); + } + } + return (T) ctxt.handleUnexpectedToken(handledType(), p); + } + + /** + * Helper called to support {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}: + * default implementation simply calls + * {@link #deserialize(JsonParser, DeserializationContext)}, + * but handling may be overridden. + * + * @since 2.9 + */ + protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt) throws IOException + { + // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid + // either supporting nested arrays, or to cause infinite looping. + if (p.hasToken(JsonToken.START_ARRAY)) { + String msg = String.format( +"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s", + ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY, + "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS"); + @SuppressWarnings("unchecked") + T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg); + return result; + } + return (T) deserialize(p, ctxt); + } + /* /********************************************************** /* Helper methods for sub-classes, parsing: while mostly @@ -616,36 +711,6 @@ protected final String _parseString(JsonParser p, DeserializationContext ctxt) t return (String) ctxt.handleUnexpectedToken(String.class, p); } - /** - * Helper method that may be used to support fallback for Empty String / Empty Array - * non-standard representations; usually for things serialized as JSON Objects. - * - * @since 2.5 - */ - @SuppressWarnings("unchecked") - protected T _deserializeFromEmpty(JsonParser p, DeserializationContext ctxt) - throws IOException - { - JsonToken t = p.currentToken(); - if (t == JsonToken.START_ARRAY) { - if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { - t = p.nextToken(); - if (t == JsonToken.END_ARRAY) { - return null; - } - return (T) ctxt.handleUnexpectedToken(handledType(), p); - } - } else if (t == JsonToken.VALUE_STRING) { - if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) { - String str = p.getText().trim(); - if (str.isEmpty()) { - return null; - } - } - } - return (T) ctxt.handleUnexpectedToken(handledType(), p); - } - /** * Helper method called to determine if we are seeing String value of * "null", and, further, that it should be coerced to null just like @@ -674,78 +739,6 @@ protected final boolean _isPosInf(String text) { protected final boolean _isNaN(String text) { return "NaN".equals(text); } - /* - /********************************************************** - /* Helper methods for sub-classes regarding decoding from - /* alternate representations - /********************************************************** - */ - - /** - * Helper method that allows easy support for array-related {@link DeserializationFeature}s - * `ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT` and `UNWRAP_SINGLE_VALUE_ARRAYS`: checks for either - * empty array, or single-value array-wrapped value (respectively), and either reports - * an exception (if no match, or feature(s) not enabled), or returns appropriate - * result value. - *

- * This method should NOT be called if Array representation is explicitly supported - * for type: it should only be called in case it is otherwise unrecognized. - *

- * NOTE: in case of unwrapped single element, will handle actual decoding - * by calling {@link #_deserializeWrappedValue}, which by default calls - * {@link #deserialize(JsonParser, DeserializationContext)}. - * - * @since 2.9 - */ - protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException - { - JsonToken t; - if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) { - t = p.nextToken(); - if (t == JsonToken.END_ARRAY) { - if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { - return getNullValue(ctxt); - } - } - if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - final T parsed = deserialize(p, ctxt); - if (p.nextToken() != JsonToken.END_ARRAY) { - handleMissingEndArrayForSingle(p, ctxt); - } - return parsed; - } - } else { - t = p.currentToken(); - } - @SuppressWarnings("unchecked") - T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, null); - return result; - } - - /** - * Helper called to support {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}: - * default implementation simply calls - * {@link #deserialize(JsonParser, DeserializationContext)}, - * but handling may be overridden. - * - * @since 2.9 - */ - protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt) throws IOException - { - // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid - // either supporting nested arrays, or to cause infinite looping. - if (p.hasToken(JsonToken.START_ARRAY)) { - String msg = String.format( -"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s", - ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY, - "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS"); - @SuppressWarnings("unchecked") - T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg); - return result; - } - return (T) deserialize(p, ctxt); - } - /* /**************************************************** /* Helper methods for sub-classes, coercions