Ilia
Ilia

Reputation: 1

Deserialize JSON array into Map using Jackson in Java

I'm using fasterXML's Jackson (v2.3.3) library to deserialize and serialize a custom class. The class is defined as following:

public class Person {

  private String name;
  private Map<String, Person> children;

  // lots of other fields of different types with no issues
}

the keys of map children are the name fields. I receive data in JSON with each person object structured as following (I have omitted the other fields):

{"name":"Bob", "children":[{"name":"Jimmmy"},{"name":"Judy"}]}

(Many Fields such as children are optional and aren't serialized when null)

I have been storing children in a List<Person> so far with no issues, but many new use cases need to have access to the set of names or to a specific Person using his name as key. This is why I have decided to store them using a Map.

After some research, I think the best way is to use Annotations @JsonDeserialize and @JsonSerialize with a JsonDeserializer and JsonSerializer as parameter respectively for the field children:

public class Person {

  private String id;

  @JsonSerialize(using=MySerializer.class)
  @JsonDeserialize(using=MyDeserializer.class)
  private Map<String, Person> friends;

  // lots of other fields
}

My question is: Does such a JsonSerializer/JsonDeserializer exist and if not, how do I define one?


edit: I have started implementing a custom Deserializer, but I get this exception:

com.fasterxml.jackson.databind.JsonMappingException: Class has no default (no arg) constructor

which is weird because I have defined a default constructor. Here is my custom Deserializer:

public class MyDeserializer extends JsonDeserializer<Map<String, Person>> {

    public MyDeserializer() {
    }

    @Override
    public Map<String, Person> deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
      JsonNode personsNodeArray = jp.getCodec().readTree(jp);
      Map<String, Person> newChildren = null;
      if (personsNodeArray.isArray() && personsNodeArray.size() > 0) {
        newChildren = new HashMap<String, Person>();
        for (JsonNode personNode : personsNodeArray) {
          String id = personNode.get("name").asText();
          // jsonMapper is a default ObjectMapper
          newChildren.put(id, jsonMapper.readValue(personNode.toString(), Person.class));
        }
      }
      return newChildren;
    }
  }

Upvotes: 2

Views: 3102

Answers (1)

Alexey Gavrilov
Alexey Gavrilov

Reputation: 10853

You can also consider reading children information as a collection of persons with subsequent conversion into a map. You can define a setter method (or a constructor parameter) to accept a List<Person> and then put each element into the Map<String, Person> children field. That would avoid unnecessary complexity of custom serialisation.

Here is an example:

public class JacksonChildren {
    public static final String JSON = "{\"name\":\"Bob\", \"children\":[{\"name\":\"Jimmmy\"}," +
            "{\"name\":\"Judy\"}]}";

    public static class Person {
        public String name;
        private Map<String, Person> children = new HashMap<>();

        public void setChildren(final List<Person> children) {
            for (Person p : children) {
                this.children.put(p.name, p);
            }
        }

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", children=" + children +
                    '}';
        }
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.readValue(JSON, Person.class));
    }

}

Output:

Person{name='Bob', children={Judy=Person{name='Judy', children={}}, Jimmmy=Person{name='Jimmmy', children={}}}}

Upvotes: 1

Related Questions