Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS only works for POJOs, Maps #994

Closed
nottmey opened this issue Nov 2, 2015 · 8 comments
Closed
Milestone

Comments

@nottmey
Copy link

nottmey commented Nov 2, 2015

Documentation of DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS only mentiones exceptional behavior for more than one value in the array ("If more than one value is found in the array, a JsonMappingException is thrown."). But trying to parse { "value" : [] } with value as String produces the following Stacktrace: (Parsing as null might be expected instead)

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of END_ARRAY token
 at [Source: { "value" : [] }; line: 1, column: 14] (through reference chain: app.Data["value"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:835)
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseString(StdDeserializer.java:809)
    at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:35)
    at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:12)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:523)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:95)
    at com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap.findDeserializeAndSet(BeanPropertyMap.java:285)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:248)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:136)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3562)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2578)
    at app.MainKt.main(Main.kt:23)
data class Data(var value : String? = null) // valid example container in Kotlin

This shouldn't be problematic when using DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, but it does not take precedence over DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS (bug?) and still gives me the error from above!

Are there any workarounds? I still need to map single element arrays, that sometimes appear to be empty. (I am using version 2.5.1, tested also 2.6.0: same behavior)

@nottmey
Copy link
Author

nottmey commented Nov 3, 2015

BeanDeserializerBase.java (L1239)

if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    JsonToken t = p.nextToken();
    if (t == JsonToken.END_ARRAY && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
        return null;
    }
    final Object value = deserialize(p, ctxt);
    if (p.nextToken() != JsonToken.END_ARRAY) {
        throw ctxt.wrongTokenException(p, JsonToken.END_ARRAY, 
                "Attempted to unwrap single value array for single '" + _valueClass.getName() + "' value but there was more than a single value in the array");
    }
    return value;
}

seems to have the right implementation in method deserializeFromArray(...),
but StdDeserializer (L833)

if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    p.nextToken();
    // CHECK MISSING HERE
    final String parsed = _parseString(p, ctxt);
    if (p.nextToken() != JsonToken.END_ARRAY) {
        throw ctxt.wrongTokenException(p, JsonToken.END_ARRAY, 
                "Attempted to unwrap single value array for single 'String' value but there was more than a single value in the array");
    }            
    return parsed;            
}

and StringDeserializer (L38)

if (curr == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    jp.nextToken();
    // CHECK MISSING HERE
    final String parsed = _parseString(jp, ctxt);
    if (jp.nextToken() != JsonToken.END_ARRAY) {
        throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, 
                "Attempted to unwrap single value array for single 'String' value but there was more than a single value in the array");
    }            
    return parsed;            
}

do not check for

if (token == JsonToken.END_ARRAY && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
  return null;
}

in a very similar situation.

This occurs probably for every other primitive type.

@nottmey
Copy link
Author

nottmey commented Nov 3, 2015

Problem does occur with target types:

  • Byte
  • Short
  • Integer
  • Long
  • Float
  • Double
  • Character
  • java.util.Date
  • Enums
  • (Java primitives? - can't check them now)

Problem does NOT occur with target types:

  • Custom Pojos
  • Collections

( Does not work for other modules like e.g. org.joda.time.DateTime, because they don't support UNWRAP_SINGLE_VALUE_ARRAYS anyway. Such specific cases are solvable with a custom deserializer. )

@cowtowncoder
Copy link
Member

Thank you for thorough investigation. Yes, sounds like ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT should have precedence.

As to whether empty array should result in null or exception is an interesting question: my first inclination was that exception is appropriate, but then again I can see why null would make sense.

@nottmey
Copy link
Author

nottmey commented Nov 11, 2015

Thank you for the confirmation.

  1. Therefore the discussed behavior (null or exception) should be configurable, as long as the user is willing to take the side effect of ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT (regardless of target type, empty arrays will be null).
  2. Additionally I would recommend to have an additional version of UNWRAP_SINGLE_VALUE_ARRAYS where null is the default behavior (maybe call it UNWRAP_OPTIONAL_SINGLE_VALUE_ARRAYS). This would make it explicitly configurable without side effect.

Since both versions are currently bugged (1.) or not implemented (2.), my current workaround is to intercept and preprocess the JSON file, replacing every occurence of [] with null (which is certainly not an ideal solution).

@cowtowncoder
Copy link
Member

Ok: just to make sure, OBJECT in there should follow roughly guidelines of ACCEPT_EMPTY_STRING_AS_OBJECT, although since there is no problem with null for String values, it should probably work there too.

@cowtowncoder cowtowncoder changed the title Issue with single element array unwrapping and empty arrays as null Issue with single element array unwrapping and empty arrays as null (DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS) Mar 24, 2017
@cowtowncoder cowtowncoder changed the title Issue with single element array unwrapping and empty arrays as null (DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS) DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS only works for POJOs, Maps Mar 24, 2017
cowtowncoder added a commit that referenced this issue Mar 24, 2017
@cowtowncoder cowtowncoder added this to the 2.9.0 milestone Mar 24, 2017
@cowtowncoder
Copy link
Member

Implemented full support in 2.9.0, added tests; should also be much easier to support from datatype libs as well (helper methods in StdDeserializer).
Some additional cases also supported in 2.8.8 (esp Enums and "from-string" types), but this is unfortunately bit bigger change not trivial to safely add in a patch.

@Arunkumarpandy1984
Copy link

Hi,

Enabling "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS" does not help for arrays having more than one value. Do we have a workaround for this?

While trying to deserialize -
"topValues":[["Ben",1],["Alyssa",1]] into a String present in a POJO. I am getting the following exception.

Unexpected token (VALUE_NUMBER_INT), expected END_ARRAY: Attempted to unwrap 'java.lang.String' value from an array (with DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS) but it contains more than one value
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.asg.dis.analysis.common.dom.data.Field["topValues"])

Thanks for helping.

@cowtowncoder
Copy link
Member

@Arunkumarpandy1984 Assuming topValues is String valued, no, that will not work -- only an Array with a single String value would map. There is no way to customize this further; multi-element array is assumed to be a failure since it seems likely an error in definition.
For more complicated logic like this you will need to use something like a custom deserializer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants