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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With