Skip to content

Instantly share code, notes, and snippets.

@amukherj
Last active November 19, 2017 07:55
Show Gist options
  • Save amukherj/d18ee50094ac9011314fc274d3785534 to your computer and use it in GitHub Desktop.
Save amukherj/d18ee50094ac9011314fc274d3785534 to your computer and use it in GitHub Desktop.

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.

Basic deserialization and serialization

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);

Attribute and member name mismatch

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
}

Missing and excess 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);
    }
}

Converting nested JSONs to nested objects

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;
    }
}

Converting JSONs to collections of Objects

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment