33

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
  • 7,680
  • 5
  • 36
  • 45
Vahid
  • 1,625
  • 1
  • 18
  • 33
  • JSON property names are not in double quotes, I believe it is a typo, right? – Michal Foksa Aug 19 '16 at 15:29
  • @MichalFoksa, Thanks. I updated the JSON data. – Vahid Aug 19 '16 at 15:32
  • Nice and neat solution. I am happy I could help. I suggest you make a new answer out of it with description of your particular circumstances (2.6.x, ...) so that people can better find. – Michal Foksa Aug 19 '16 at 19:47
  • is there a way to make object behaviour List or MyClass depending on input json node. i.e. if its JsonNode we deserialize and write json using MyClass. And if its ArrayNode, we deserialize and write json using List or MyClass[] – axnet Oct 04 '22 at 10:01

5 Answers5

71

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
  • 7,680
  • 5
  • 36
  • 45
  • 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,625
  • 1
  • 18
  • 33
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
  • 11,225
  • 9
  • 50
  • 68
  • 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
5

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.

Sujith Barla
  • 51
  • 1
  • 2
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,699
  • 7
  • 36
  • 65