Search code examples
javajsonjacksondeserializationjson-deserialization

How to deserialize nested JSON array into a Map using Java's Jackson library?


I have a structure like so:

{
    "name": "user",
    "values":[["0.00207760","18.48000000"],["0.00207740","40.00000000"],["0.00207710","2.26000000"]]
}

I'd like to deserialize into a class like so using the popular Jackson library:

public class Values {

    public String name;

    public Map<BigDecimal, BigDecimal> values = new HashMap<>();

}

Where each entry in the values property becomes a key/value entry in the class' map.

However, if I attempt a simple deserialization with Jackson I get this error:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.TreeMap<java.lang.Object,java.lang.Object>` out of START_ARRAY token
 at [Source: (String)"{"name": "user","values":[["0.00207760","18.48000000"],["0.00207740","40.00000000"],["0.00207710","2.26000000"]]...
    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
    at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1442)
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1216)
    ...

How can this be accomplished using Jackson?

Thanks!

Eduardo


Solution

  • You expect Map which on JSON side is represented by JSON Object but there is a JSON Array which can be mapped by default to List, Set or array. I suggest to use List<List<BigDecimal>> in POJO and create a method which converts data to Map:

    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.json.JsonMapper;
    
    import java.io.File;
    import java.math.BigDecimal;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    public class JsonPathApp {
    
        public static void main(String[] args) throws Exception {
            File jsonFile = new File("./resource/test.json").getAbsoluteFile();
    
            ObjectMapper mapper = JsonMapper.builder().build();
            Values values = mapper.readValue(jsonFile, Values.class);
            System.out.println(values.getValuesAsMap());
        }
    }
    
    class Values {
    
        private String name;
        private List<List<BigDecimal>> values;
    
        public Map<BigDecimal, BigDecimal> getValuesAsMap() {
            return values.stream().collect(Collectors.toMap(
                    k -> k.get(0),
                    v -> v.get(1),
                    (u, v) -> {
                        throw new IllegalStateException(String.format("Duplicate key %s", u));
                    },
                    LinkedHashMap::new));
        }
    
        // getters, setters, toString
    }
    

    Above code prints:

    {0.00207760=18.48000000, 0.00207740=40.00000000, 0.00207710=2.26000000}
    

    In other case, you need to implement a custom deserialiser for a Map and register it for this field using @JsonDeserialize annotation.