This is a short tutorial on converting JSON to objects of Java classes (deserialization) and back (serialization), using Java. The Jackson library, one of the more popular JSON libraries used in Java, provides a very elegant way to do this using annotations and reflection.
Imagine you have some JSON describing a book in your library. How would you write a Book class that can be deserialized from this JSON?
{
"title": "The Linux Programming Interface",
"author": "Michael Kerrisk",
"publisher": "No Starch Press",
"isbn": "978-1-59327-220-3"
}
You want to create a class called Book, that roughly corresponds to this JSON.
public class Book
{
private String title;
private String author;
private String publisher;
private String isbn;
Book() {}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
// ... getters and setters for the other attributes
}
Finally, to deserialize the JSON to class, you will do this:
import com.fasterxml.jackson.databind.ObjectMapper;
String json = "...."; // json content
ObjectMapper mapper = new ObjectMapper();
Book lpiBook = mapper.convertValue(mapper.readTree(json), Book.class); // readTree can throw IOException if json is illegal
With just these few lines of code, you can convert the JSON text into an instance of the class Book. It works because the names of the keys in the JSON and the names of the member variables in the Book
class match exactly.
Of course, you could as well create an instance of Book
manually and convert it to JSON.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
Book theLPI = new Book();
theLPI.setTitle("The Linux Programming Interface");
theLPI.setAuthor("Michael Kerrisk");
theLPI.setPublisher("No Starch Press");
theLPI.setIsbn("978-1-59327-220-3");
ObjectMapper mapper = new ObjectMapper();
ObjectNode bookNode = mapper.convertValue(theLPI, ObjectNode.class);
String json = mapper.writeValueAsString(bookNode);
It may sometimes happen that you want to make an existing class serializable from JSON, and the member variable names in the class and the attribute names in the JSON don't match. Say if the JSON looked like this (with the key names in Spanish rather than English):
{
"titulo": "The Linux Programming Interface",
"autor": "Michael Kerrisk",
"editor": "No Starch Press",
"ISBN": "978-1-59327-220-3"
}
In this case you need a little annotation in your class:
import com.fasterxml.jackson.annotation.*;
public class Book
{
@JsonProperty("titulo")
private String title;
@JsonProperty("autor")
private String author;
@JsonProperty("editor")
private String publisher;
@JsonProperty("ISBN")
private String isbn;
Book() {}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
// ... getters and setters for the other attributes
}
Things get interesting when you don't know what all attributes might be there in the JSON besides the ones you specified, or if some of the attributes you specified will be necessarily present.
For example, the following JSON will fail to deserialize into the Book class defined above due to the extra attribute publication_year
which the Book
class does not know of.
{
"title": "The Linux Programming Interface",
"author": "Michael Kerrisk",
"publisher": "No Starch Press",
"isbn": "978-1-59327-220-3",
"publication_year": "2010",
}
A simple technique to ignore attributes you don't know of is to use the @JsonIgnoreProperties annotation with ignoreUnknown argument set to true:
import com.fasterxml.jackson.annotation.*;
@JsonIgnoreProperties(ignoreUnknown=true)
public class Book
{
// rest unchanged
}
A missing attribute results in the corresponding value being set to null (for Objects like Strings) or 0 for numeric values.
What if you want to capture excess attributes in some sort of a map? Here is one technique - where we only expect the "title" attribute to be there and capture all other attributes without discrimination:
import com.fasterxml.jackson.annotation.*;
@JsonIgnoreProperties(ignoreUnknown=true)
public class Book
{
public String title;
public Map<String, Object> properties = new HashMap<String, Object>();
@JsonCreator
public Book(@JsonProperty("title") String title) {
this.title = title;
}
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
@JsonAnyGetter
public Map<String, Object> getAll() {
return properties;
}
@JsonAnySetter
public void set(String attr, Object value) {
properties.put(str, value);
}
}
JSON can have nested JSONs. Say we could capture additional info about the author like this:
{
"title": "The Linux Programming Interface",
"author": {
"name": "Michael Kerrisk",
"residence": "Germany"
}
"publisher": "No Starch Press",
"isbn": "978-1-59327-220-3",
"publication_year": "2010"
}
You can create an Author
class and put an instance of it in the Book
class.
public class Author
{
private String name;
private String residence;
// constructor
// getters
// setters
}
Your Book
class changes only slightly.
public class Book
{
private String title;
private Author author;
// other fields
// constructor
public Author getAuthor() {
return author;
}
public void setAuthor(Author auth) {
this.author = author;
}
}
We frequently want to convert a JSON into a collection of objects, say a collection of Books
.
[
{
"title": "The Linux Programming Interface",
"author": {
"name": "Michael Kerrisk",
"residence": "Germany"
},
"publisher": "No Starch Press",
"isbn": "..."
},
{
"title": "Thinking Fast and Slow",
"author": {
"name", "Daniel Kahneman",
"residence": "Israel"
},
"publisher": "Farrar, Straus and Giroux",
"isbn": "..."
},
...
]
To convert this to a List
of Book
objects, we could write:
import com.fasterxml.jackson.databind.type.TypeFactory;
ObjectMapper mapper = new ObjectMapper();
List<Book> books = mapper.convertValue(jsonText,
TypeFactory.defaultInstance().constructCollectionType(List.class, Book.class));
Likewise, you could deserialize to Set
and Map
.