Module 3 - JSON Serialization
Module Overview
Learn about JSON serialization and deserialization in Java using the Jackson library, a powerful tool for converting between Java objects and JSON format.
Learning Objectives
- Implement code that serializes a class to JSON
- Implement code that deserializes a provided JSON string to an object of the class it represents
- Explain why serialization is used to transfer data
- Define the terms serialization and deserialization
Serializing Java Objects to JSON
Serialization and deserialization

As shown in Figure 1, serialization is the mechanism for converting the state of an object to another representation. We're going to focus on serializing objects to JSON. Once you've converted an object to JSON, you can store it in a file, memory, or database.

As shown in Figure 2, deserialization is the reverse process, recreating the Java object from the stored JSON.
Serialization is often used to persist an object. Persistence is the ability to maintain the state of an object after a runtime session ends. For example, if you have a main() method in Java where you create an object and manipulate its state, that state is lost as soon as the program stops running. Each time you run the main() method, the object will be recreated in its original state. However, if you wanted the changed state to persist across runtime sessions, you could serialize the object. The next time you run the main() method, you could then deserialize the object and get the saved state. Databases, which you already have experience with, are one common way of persisting information!
In our example, you can see that serialization is the process of saving the state of an object and deserialization is the process of retrieving the state of an object from an earlier save. This process of serializing an object is also called marshalling an object. Likewise, extracting a data structure, or deserializing, is also called unmarshalling. These terms are often used interchangeably, but they're not quite the same thing---serializing is actually a type of marshalling. The difference between them, however, is subtle and difficult, and we will not focus on it in this reading.
Why do we serialize?
We use serialization any time we want to save or send state. A document editing program might serialize an "undo" list, so when you reload a document you don't lose your editing history. A program that does many long-running computations might serialize the calculations performed so far, so if the user interrupts the program, you can continue where you left off. A command-line tool with many options and steps might serialize its progress, so a user could type something like "tool --continue" after fixing a problem. A call to an API across the network might serialize the request in order to send it to the service.
We choose to use JSON because pretty much EVERYTHING can handle it. We often serialize and deserialize between Java applications, but data from a Java application can be serialized and then read by a different type of application. A Java application might serialize objects to a file and then a Python script can open that file and deserialize the data because it is in a shared language! It doesn't need to be a file, either: we can serialize an object and transfer it to another computer over a network connection. The data may be passed between many computers with different hardware and operating systems before it reaches the destination, which will still be able to deserialize it regardless of what language, operating system, or hardware it uses.
Most major languages have libraries that aid in writing to and from JSON. In Java we use the Jackson library.
Serializing a class in Java
You're creating a video game in Java that keeps track of the current state of your player. The state of your player may change depending on how many hit points they have, the current items in their inventory, etc. Each time you close and reload the game, these values need to be saved, meaning the state of your character must persist across play sessions. We can serialize the state of the character and store it in a file or database. When we reload the game, we can deserialize the character object from the most recently saved state. Now all the most recent character data is available from the saved state!
Let's look at an example of how to serialize a Java object to JSON. The Character class is defined below.
public class Character {
private String characterName;
private int hitPoints;
private List<String> inventory;
public String getCharacterName() {
return characterName;
}
public void setCharacterName(String characterName) {
this.characterName = characterName;
}
public int getHitPoints() {
return hitPoints;
}
public void setHitPoints(int hitPoints) {
this.hitPoints = hitPoints;
}
public List<String> getInventory() {
return inventory;
}
public void setInventory(List<String> inventory) {
this.inventory = inventory;
}
}
Serializing and deserializing to JSON with Jackson
We can serialize and deserialize objects using the Jackson library. This library provides the ObjectMapper class, which can create JSON from Java objects (serialize), and parse JSON from a String and create a Java object representing the parsed JSON (deserialize).
To serialize an object with Jackson we use the method writeValueAsString(), which accepts an instance of a serialized class and returns a String.
Character character = new Character();
character.setCharacterName("Jane");
character.setHitPoints(41);
List<String> inventory = new ArrayList<String>();
inventory.add("sword");
inventory.add("shield");
character.setInventory(inventory);
ObjectMapper objectMapper = new ObjectMapper();
String serializedCharacter = objectMapper.writeValueAsString(character);
The above code snippet declares a new Character instance, character, and sets all its values. It then declares a new instance of the ObjectMapper class, objectMapper, and uses that instance to call the writeValueAsString() method. The character instance we created is passed into the method, serializing character into the JSON String, serializedCharacter.
If we print out serializedCharacter, we get the following:
{"characterName":"Jane","hitPoints":41,"inventory":["sword","shield"]}
JSON stores the serialized character with the field name followed by the corresponding value. Each value in the inventory ArrayList is also stored separately between square brackets.
Let's take a look at another Character that has only its name field set.
Character character2 = new Character();
character2.setCharacterName("Jackson");
ObjectMapper objectMapper = new ObjectMapper();
String serializedCharacter2 = objectMapper.writeValueAsString(character2);
If we print out serializedCharacter2, we get the following:
{"characterName":"Jackson","hitPoints":0,"inventory":null}
Notice that the unset fields are included, but set to their default values.
To deserialize a serialized object, we use the Jackson method readValue(), which accepts a String and a class and returns an instance of that class.
Character deserializedCharacter = objectMapper.readValue(serializedCharacter, Character.class);
The above code snippet uses the ObjectMapper instance, objectMapper, to call the readValue() method. The method is given the serializedCharacter string from the previous code snippet and the Character class. Notice, because the JSON representation has no information about what class this JSON maps to, we must provide it the readValue method. The deserialized Character is returned into the new instance of Character, deserializedCharacter.
We've seen very similar functionality before. The DynamoDBMapper acts just like the ObjectMapper in that it serializes Java objects to a string representation, as well as deserializes string representations to Java objects. The DynamoDB string representation isn't typical JSON though, it is DynamoDB JSON. For an item with one attribute called id, with a string value id-123456, the DynamoDB JSON would be:
{"id":{"S":"id-123456"}}
Just like Jackson, when we read from DynamoDB, via load, query, or scan we also must specify the class we want to deserialize to!
Note: if you want to see the DynamoDB JSON for an item, click on the item in the console to open the 'Edit item' popover. Select 'Text' instead of 'Tree', and then check the box for DynamoDB JSON.
Since serialized JSON is a String, you could serialize an object and save it in a DynamoDB S field. Since the format is different, DynamoDB would be unable to use the object's structure for filtering and querying. We prefer to store an object's attributes as separate DynamoDB fields.
Caution: Limitations
We won't encounter these problems in ATA code. We'll describe them briefly here to help you recognize them if you encounter them elsewhere. More details are readily available through web searches.
Serializing circular references, like a Child class with a reference to a Parent that has a reference back to the same Child, will cause an exception. You can mark one of the attributes with @JsonIgnore to break the loop.
You also can't serialize a private field without a getter. Adding a getter is the easiest way to fix this.
When deserializing, you need some way to build the starting object. Classes that don't have a public no-args constructor can't be deserialized. Like missing getters, adding the constructor is the easiest fix.
Deserializing an attribute with an interface type has the same problem: there's no way to build the starting object. If you know you'll always want to use the same concrete class, you can use the @JsonDeserialize annotation.
Deserializing won't automatically handle inheritance hierarchies. The @JsonTypeInfo and @JsonSubTypes annotations can handle these situations, and can also resolve the interface deserialization problem we described earlier.
Optional: Java's own byte stream serialization
Java defines its own system for serializing and deserializing objects. While Jackson doesn't provide any information about the class, Java's ObjectOutputStream and ObjectInputStream verify that they're reading the same class that was written. However, they can only serialize classes that implement Serializable and provide a private static final long serialVersionUID instance variable that acts as a sort of version number. In ATA, we'll use Jackson for our serialization needs.
Conclusion
In this reading, you learned about serializing Java objects to JSON and then deserializing JSON back to Java objects. Serialization is very useful for sending and receiving data across networks and persisting data across runtime sessions. Additionally, you now know about the importance of a serialVersionUID to keep track of class versions.