28

Is there a way to use annotation on a List property in a class to use ACCEPT_SINGLE_VALUE_AS_ARRAY in Jackson? I'm using Spring and getting the below exception

nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token

Assume I have a class as below:

public class MyClass {

    private List < String > value;
}

And my JSON structures are as below:

case 1:

[{"operator": "in", "value": ["Active"], "property": "status"}]

case 2:

[{"operator": "like", "value": "aba", "property": "desc"}]

What annotation should I use to let the framework know I want these 2 cases to be treated the same when deserializing.

UPDATE: I moved the updates to an answer in this post for more clarity.

Ömer Erden
  • 6,171
  • 5
  • 26
  • 40
Vahid
  • 1,181
  • 1
  • 11
  • 24

5 Answers5

60

You can use @JsonFormat annotation,

public class MyClass {

    @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
    private List<String> value;

}

To work with this you need to have Jackson version min 2.7.0. You can also use other available JsonFormat Features

For version 2.6.x

@Autowired private ObjectMapper mapper;
//...

mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
  • Add this code to your Initializer Class.
  • Or you can directly configure Jackson in your Bean Configuration

These would solve the issue but it will be activated for every deserialization process.

Ömer Erden
  • 6,171
  • 5
  • 26
  • 40
  • I tried your solution but its not working. I have jackson annotation 2.6.0. I'm getting same exception – Vahid Aug 19 '16 at 17:39
  • @Vahid I tested for 2.6.x, its not working as you say, but for 2.7.x and higher its working as you expected, it could be a bug for 2.6.x but i am not sure because they have this functionality in their documentation, if your project depended on 2.6.x , we can find a way to enable this functionality in 2.6.x. – Ömer Erden Aug 19 '16 at 18:40
  • Thanks for confirming. Due to the project dependency restrictions I cannot upgrade. For now I'm going to fix it using a custom deserializer. I'll post my solution in an update. – Vahid Aug 19 '16 at 18:53
  • I didn't try but I think I can get hold of it. – Vahid Aug 19 '16 at 19:08
12

I'm just answering my own question for clarity. One of the answers was to upgrade to the higher version so that I can use annotations. I cannot do it due to dependency restrictions of my project.

As a result, based on Michal Foksa answer I solved my problem by creating a custom deserializer. Its as below:

On my property:

@JsonDeserialize(using = CustomStringDeserializer.class)
private List<String> value;

And my Deserializer:

public class CustomStringDeserializer extends JsonDeserializer<List<String>>{

    @Override
    public List<String> deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(DeserializationFeature. ACCEPT_SINGLE_VALUE_AS_ARRAY);
        return mapper.readValue(p, List.class);
    }

}
Vahid
  • 1,181
  • 1
  • 11
  • 24
7

I would create a custom JsonDeserializer where I would deal with both cases and annotate value class property with the deserializer.

Deserializer:

public class StringListDeserializer extends JsonDeserializer<List<String>>{

    @Override
    public List<String> deserialize(JsonParser parser, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        List<String> ret = new ArrayList<>();

        ObjectCodec codec = parser.getCodec();
        TreeNode node = codec.readTree(parser);

        if (node.isArray()){
            for (JsonNode n : (ArrayNode)node){
                ret.add(n.asText());
            }
        } else if (node.isValueNode()){
            ret.add( ((JsonNode)node).asText() );
        }
        return ret;
    }
}

MyClass:

public class MyClass {
    .
    @JsonDeserialize(using = StringListDeserializer.class)
    private List<String> value;
    .
}

Hope it helps.

BTW: If there is a way how to deal with such a case with just an annotation, I also want to know it.

Michal Foksa
  • 9,725
  • 8
  • 43
  • 63
  • Thanks for pointing to the right direction. I solved my problem using your idea of custom deserializer. In the deserialize method we can fix the issue simpler than your method. Please check my update on the post. – Vahid Aug 19 '16 at 19:21
3

For spring projects adding

spring.jackson.deserialization.accept-single-value-as-array=true 

in property files will work for all deserializations handled by spring even on then controller class methods where @RequestBody for the APIs.

1

If this project is a Spring project then you can put this property in you application.properties:

spring.jackson.deserialization.UNWRAP_SINGLE_VALUE_ARRAYS=true

javaPlease42
  • 4,263
  • 6
  • 33
  • 62