Search code examples
javajsonfasterxml

Map an array of JSON objects to a java.util.Map and vice versa


The question is how to map an array of JSON objects to a java.util.Map where each key would be some specified property of an object and the value is the object itself.

JSON:

{"items": [{"field1": 1, "field2": "Hello"}, {"field1": 2, "field2":"World"}]}

Java POJO:

public class Storage {
    private Map<Integer, Item> items;
}

public class Item {
    private Integer field1;
    private String field2;
}

So is there a some way to specify to ObjectMapper that it should use field1 property of each JSON object as key when deserializing array of items to the Map?


Solution

  • How to deserialize a JSON string

    You can use Jackson to deserialize a JSON string:

    For example if you have class Foo

    public class Foo {
    
       private Bar[] items;
    
       // Constructor / Getters & Setters
    
    } 
    

    And that class has an array of class Bar

     public class Bar {
    
         private int field1;
         private String field2;
    
    
         // Constructor / Getters & Setters
    
     }
    

    Where the field names match those in your JSON string then you can do the following to convert it:

    String jsonString = "{\"items\": [{\"field1\": 1, \"field2\": \"Hello\"}, {\"field1\": 2, \"field2\":\"World\"}]}";
    
    ObjectMapper mapper = new ObjectMapper();
    
    Foo foo = mapper.readValue(jsonString, Foo.class);
    

    If you are using Maven, the following dependency would be required in your pom.xml:

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
    

    Approaches to solve your problem:

    Option 1 - Custom Deserializer

    Write a custom JsonDeserializer to deserialize your JSON string into a Storage object with a field items of type Map<String,Item>

     public class CustomDeserializer extends JsonDeserializer<Storage> {
    
        @Override
        public Storage deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
                throws IOException {
    
            Map<Integer, Item> map = new HashMap<>();
    
            ObjectCodec oc = jsonParser.getCodec();
            JsonNode rootNode = oc.readTree(jsonParser);
            JsonNode items = rootNode.get("items");
    
            for (int i = 0; i < items.size(); i++) {
    
                JsonNode childNode = items.get(i);
    
                Item item = new Item(childNode.get("field1").asInt(), childNode.get("field2").asText());
    
                map.put(item.getField1(), item);
            }
    
            return new Storage(map);
        }
    }
    

    You would then annotate your Storage class with the following:

    @JsonDeserialize(using = CustomDeserializer.class)
    

    Your Storage class would look something like;

    @JsonDeserialize(using = CustomDeserializer.class)
    public class Storage {
    
       private Map<Integer, Item> items;
    
       public Storage(Map<Integer, Item> map) {
        this.items = map;
       }
    
       ...
    
    }
    

    Option 2 - Create Map post deserialization

    Deserialize the JSON string into a Storage object with an array of Item as described at the beginning and then construct your Map<Integer, Item> after.

    Hope this helps.