skip to Main Content

This is the JSON I’m working on:

{
    "default": "ignore-me",
    "catalog": [
        {
            "id": "object-1",
            "name": "Object 1"
        },
        {
            "id": "object-3",
            "name": "Object 3"
        },
        {
            "id": "object-2",
            "name": "Object 2"
        }
    ]
}

I can parse it in Java using Jackson with:

ObjectMapper mapper = new ObjectMapper();
Catalogs catalogs = mapper.readValue(new File("/catalogs.json"), Catalogs.class);

Java model for Catalogs.java and Catalog.java:

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;

public class Catalogs {

  @JsonProperty("default")
  private String defaultField;

  private List<Catalog> catalogList;

  // getters / setters
}
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Catalog {

  private String id;
  private String name;

  // getters / setters

My goal is to convert List<Catalog> catalogList into something like a HashMap which allows for quicker access to entries when the id is known ahead of time, rather then having to iterate through all entries.

So I’d like to do something like:

Catalog catalog = catalogs.getCatalogList().get("object-1");

Is it possible for Jackson to do this conversion of type automatically during deserialization?

3

Answers


  1. You need to create getter/setter and convert to/from map there. Also provide getter for map.

    Login or Signup to reply.
  2. Custom Deserializer Solution

    If you want to deserialize a List of Catalog as a HashMap, you could define a custom deserializer extending the StdDeserializer class and annotate the field catalogs with the @JsonDeserialize annotation. The deserialzer will be responsible for converting the List structure to a Map according to your logic.

    public class CatalogListDeserializer extends StdDeserializer<HashMap<String, Catalog>> {
    
        public CatalogListDeserializer() {
            this(null);
        }
    
        public CatalogListDeserializer(Class<?> c) {
            super(c);
        }
    
        @Override
        public HashMap<String, Catalog> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
            HashMap<String, Catalog> catalogMap = new HashMap<>();
            JsonNode node = p.getCodec().readTree(p);
            node.iterator().forEachRemaining(item -> {
                try {
                    catalogMap.put(item.get("id").asText(), ctxt.readTreeAsValue(item, Catalog.class));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            return catalogMap;
        }
    }
    

    Custom Serializer to maintain List structure in Json

    Furthermore, assuming that you want to maintain the same list structure within the json, and therefore not serializing catalogs as a Map but still as a List, you need to define a custom serializer as well. In the custom serializer class, you would basically implement the opposite logic of the other class, i.e. creating a List from a given Map, and serialize it as an array of Catalog elements. The catalogs field must be annotated with the @JsonSerialize annotation.

    public class CatalogListSerializer extends StdSerializer<HashMap<String, Catalog>> {
    
        public CatalogListSerializer() {
            this(null);
        }
    
        public CatalogListSerializer(Class<HashMap<String, Catalog>> c) {
            super(c);
        }
    
        @Override
        public void serialize(HashMap<String, Catalog> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            if (value == null) {
                gen.writeStartObject();
                gen.writeObjectField("catalog", null);
                gen.writeEndObject();
                return;
            }
    
            List<Catalog> catalogList = value.values().stream().collect(Collectors.toList());
            gen.writeStartArray();
            for (Catalog catalog : catalogList) {
                gen.writeObject(catalog);
            }
            gen.writeEndArray();
        }
    }
    

    Catalogs Pojo

    public class Catalogs {
    
            @JsonProperty("default")
            private String defaultField;
    
            @JsonProperty("catalog")
            @JsonDeserialize(using = CatalogListDeserializer.class)
            @JsonSerialize(using = CatalogListSerializer.class)
            private HashMap<String, Catalog> catalogList;
    
            //no-arg and all-args constructors
    
            //getters and setters
    }
    

    Demo

    Here is a demo at OneCompiler.

    Login or Signup to reply.
  3. One possible solution is to using Jackson to parse the JSON into JsonNode first. Then use another JSON library to perform re-structure before converting it to POJO.

    https://github.com/octomix/josson

    Revised Class Catalogs

    public class Catalogs {
        @JsonProperty("default")
        private String defaultField;
        @JsonProperty("catalog")
        private Map<String, Catalog> catalogList;
        // getters, setters, toString
    

    Josson function:

    • field() modify a field with new value
    • map() construct new object, syntax id::? retrieve the value of id to become the key name and ? denote the current node
    • mergeObjects() merge all objects inside an array to build a new object
    JsonNode input = new ObjectMapper().readTree(new File("/catalogs.json"));
    JsonNode node = Josson.create(input).getNode("field(catalog.map(id::?).mergeObjects())");
    System.out.println("JsonNode:n" + node.toPrettyString());
    
    Catalogs catalogs = Josson.readValueFor(node, Catalogs.class);
    Catalog catalog = catalogs.getCatalogList().get("object-1");
    System.out.println("catalog:n" + catalog);
    

    Output

    JsonNode:
    {
      "default" : "ignore-me",
      "catalog" : {
        "object-1" : {
          "id" : "object-1",
          "name" : "Object 1"
        },
        "object-3" : {
          "id" : "object-3",
          "name" : "Object 3"
        },
        "object-2" : {
          "id" : "object-2",
          "name" : "Object 2"
        }
      }
    }
    catalog:
    Catalog{id='object-1', name='Object 1'}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search