How to enforce ACCEPT_SINGLE_VALUE_AS_ARRAY in jackson's deserialization process using annotation
Asked Answered
C

5

35

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.

Cellist answered 19/8, 2016 at 14:25 Comment(4)
JSON property names are not in double quotes, I believe it is a typo, right?Tocsin
@MichalFoksa, Thanks. I updated the JSON data.Cellist
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.Tocsin
is there a way to make object behaviour List<MyClass> 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<MyClass> or MyClass[]Monosepalous
C
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);
    }

}
Cellist answered 19/8, 2016 at 20:20 Comment(1)
I would accept this answer If it is still correct one, so that people can see which one is actual solution.Tocsin
T
72

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.

Thrombin answered 19/8, 2016 at 16:9 Comment(4)
I tried your solution but its not working. I have jackson annotation 2.6.0. I'm getting same exceptionCellist
@Cellist 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.Raffia
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.Cellist
I didn't try but I think I can get hold of it.Cellist
C
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);
    }

}
Cellist answered 19/8, 2016 at 20:20 Comment(1)
I would accept this answer If it is still correct one, so that people can see which one is actual solution.Tocsin
T
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.

Tocsin answered 19/8, 2016 at 15:48 Comment(1)
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.Cellist
R
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.

Rorke answered 19/2, 2021 at 15:44 Comment(0)
H
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

Humbertohumble answered 29/7, 2019 at 21:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.