Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize nested array as ArrayList with Jackson

Tags:

I have a piece of JSON, that looks like this:

{   "authors": {     "author": [       {         "given-name": "Adrienne H.",         "surname": "Kovacs"       },       {         "given-name": "Philip",         "surname": "Moons"       }     ]    }  } 

I have created a class to store Author information:

public class Author {     @JsonProperty("given-name")     public String givenName;     public String surname; } 

And two wrapper classes:

public class Authors {     public List<Author> author; }  public class Response {     public Authors authors; } 

This is working, but having two wrapper classes seems to be unnecessary. I want to find a way to remove Authors class and have a list as a property of Entry class. Is something like that is possible with Jackson?

Update

Solved that with custom deserializer:

public class AuthorArrayDeserializer extends JsonDeserializer<List<Author>> {      private static final String AUTHOR = "author";     private static final ObjectMapper mapper = new ObjectMapper();     private static final CollectionType collectionType =             TypeFactory             .defaultInstance()             .constructCollectionType(List.class, Author.class);      @Override     public List<Author> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)             throws IOException, JsonProcessingException {          ObjectNode objectNode = mapper.readTree(jsonParser);         JsonNode nodeAuthors = objectNode.get(AUTHOR);          if (null == nodeAuthors                     // if no author node could be found                 || !nodeAuthors.isArray()           // or author node is not an array                 || !nodeAuthors.elements().hasNext())   // or author node doesn't contain any authors             return null;          return mapper.reader(collectionType).readValue(nodeAuthors);     } } 

And using it like this:

@JsonDeserialize(using = AuthorArrayDeserializer.class) public void setAuthors(List<Author> authors) {     this.authors = authors; } 

Thanks @wassgren for the idea.

like image 726
Dmitry Avatar asked Jan 12 '15 04:01

Dmitry


1 Answers

I see at least two approaches to do this if you want to get rid of wrapper classes. The first is to use the Jackson Tree Model (JsonNode) and the second is to use a deserialization feature called UNWRAP_ROOT_VALUE.


Alternative 1: Use JsonNode

When deserializing JSON using Jackson there are multiple ways to control what type of objects that are to be created. The ObjectMapper can deserialize the JSON to e.g. a Map, JsonNode (via the readTree-method) or a POJO.

If you combine the readTree-method with the POJO conversion the wrappers can be completely removed. Example:

// The author class (a bit cleaned up) public class Author {     private final String givenName;     private final String surname;      @JsonCreator     public Author(             @JsonProperty("given-name") final String givenName,             @JsonProperty("surname") final String surname) {          this.givenName = givenName;         this.surname = surname;     }      public String getGivenName() {         return givenName;     }      public String getSurname() {         return surname;     } } 

The deserialization can then look something like this:

// The JSON final String json = "{\"authors\":{\"author\":[{\"given-name\":\"AdrienneH.\",\"surname\":\"Kovacs\"},{\"given-name\":\"Philip\",\"surname\":\"Moons\"}]}}";  ObjectMapper mapper = new ObjectMapper();  // Read the response as a tree model final JsonNode response = mapper.readTree(json).path("authors").path("author");  // Create the collection type (since it is a collection of Authors) final CollectionType collectionType =         TypeFactory                 .defaultInstance()                 .constructCollectionType(List.class, Author.class);  // Convert the tree model to the collection (of Author-objects) List<Author> authors = mapper.reader(collectionType).readValue(response);  // Now the authors-list is ready to use... 

If you use this Tree Model-approach the wrapper classes can be completely removed.


Alternative 2: remove one of the wrappers and unwrap the root value The second approach is to remove only one of the wrappers. Assume that you remove the Authors class but keep the Response-wrapper. If you add the a @JsonRootName-annotation you can later unwrap the top-level name.

@JsonRootName("authors") // This is new compared to your example public class Response {     private final List<Author> authors;      @JsonCreator     public Response(@JsonProperty("author") final List<Author> authors) {         this.authors = authors;     }      @JsonProperty("author")     public List<Author> getAuthors() {         return authors;     } } 

Then, for your mapper simply use:

ObjectMapper mapper = new ObjectMapper();  // Unwrap the root value i.e. the "authors" mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); final Response responsePojo = mapper.readValue(json, Response.class); 

The second approach only removes one of the wrapper classes but instead the parsing function is quite pretty.

like image 100
wassgren Avatar answered Sep 28 '22 09:09

wassgren