JSON and Serialization - softwareconstruction240/softwareconstruction GitHub Wiki
🖥️ Slides
📖 Required Reading: Learn Gson
📖 Optional Reading: Douglas Crockford: The JSON Saga
JavaScript Object Notation (JSON) was conceived by Douglas Crockford in 2001 while working at Yahoo! JSON, pronounced like the name Jason, received official standardization in 2013 and 2017 (ECMA-404, RFC 8259).
JSON provides a simple, and yet effective way, to share and store data. By design JSON is easily convertible to, and from, JavaScript objects. This make it a very convenient data format when working with web technologies. Because of its simplicity, standardization, and compatibility with JavaScript, JSON has become one of the world's most popular data formats.
A JSON document contains one of the following data types:
Type | Example |
---|---|
string | "crockford" |
number | 42 |
boolean | true |
array | [null,42,"crockford"] |
object | {"a":1,"b":"crockford"} |
null | null |
Most commonly, a JSON document contains an object. Objects contain zero or more key value pairs. The key is always a string, and the value must be one of the valid JSON data types. Key value pairs are delimited with commas. Curly braces delimit an object, square brackets and commas delimit arrays, and strings are always delimited with double quotes.
Here is an example of a JSON document.
{
"class": {
"title": "web programming",
"description": "Amazing"
},
"enrollment": ["Marco", "Jana", "فَاطِمَة"],
"start": "2025-02-01",
"end": null
}
JSON is always encoded with UTF-8. This allows for the representation of global data.
The Gson
library was created by Google in order to support JSON in Java code. Once you have installed the Gson library you can convert a Java String to a Java object, or a Java object to a string. The following code demonstrates how to do this.
public class GsonExample {
public static void main(String[] args) {
var obj = Map.of(
"name", "perry",
"year", 2264,
"pets", List.of("cat", "dog", "fish")
);
var serializer = new Gson();
var json = serializer.toJson(obj);
System.out.println("JSON: " + json);
var objFromJson = serializer.fromJson(json, Map.class);
System.out.println("Object: " + objFromJson);
}
}
Running this code will output the following.
> java JsonExample
JSON: {"year":2264,"pets":["cat","dog","fish"],"name":"perry"}
Object: {year=2264.0, pets=[cat, dog, fish], name=perry}
When you call the fromJson
method to deserializing JSON into an object, you provide the class that it will use to rehydrate the JSON content.
var serializer = new Gson();
var objFromJson = serializer.fromJson(json, Map.class);
However, if the class that you are attempting to create contains fields that are interfaces or derived classes, then you must help Gson know which class should be used when it creates the backing class for the interface or derived class.
For example, consider serializing the ChessBoard
class. The chess board probably contains a field that contains a double array of ChessPieces
. If you have created subclasses of ChessPiece
, with things like Rook
or Knight
, and when you serialize out your board, the JSON will lose the representation of the derived classes. The type of piece is still represented in the type
field, but there is not Rook
or Knight
class representation and the JSON will look something like the following.
{
"squares":[
[{"color":"WHITE","type":"ROOK"},{"color":"WHITE","type":"KNIGHT"}, ... ]
[null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null],
...
[{"color":"BLACK","type":"ROOK"},{"color":"BLACK","type":"KNIGHT"}, ...]
]
}
You now have a problem when you deserialize the JSON back into Java objects. Gson won't know that it should turn the ChessPiece
class described by the ChessBoard
squares field into a specific Rook
, Knight
, or any other classes that you had before you serialized the board. So Gson will just create a bunch of ChessPiece
objects in your board array instead of a specific subclass.
You can solve this by defining a Gson TypeAdapter
that implements exactly how the JSON text should be deserialized.
To use a TypeAdapter
you create a GsonBuilder
, register the type adapter with the builder, and create the Gson serializer from the builder with the create
method. In the example below, we create a lambda function that implements the JsonDeserializer
interface of the TypeAdapter
. The function reads the JSON type
attribute from the data contained in the JSON element represented by the el
parameter. This allows the deserializer to switch on what class actually gets created form the JSON element.
public static Gson createSerializer() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(ChessPiece.class,
(JsonDeserializer<ChessPiece>) (el, type, ctx) -> {
ChessPiece chessPiece = null;
if (el.isJsonObject()) {
String pieceType = el.getAsJsonObject().get("type").getAsString();
switch (ChessPiece.PieceType.valueOf(pieceType)) {
case PAWN -> chessPiece = ctx.deserialize(el, Pawn.class);
case ROOK -> chessPiece = ctx.deserialize(el, Rook.class);
case KNIGHT -> chessPiece = ctx.deserialize(el, Knight.class);
case BISHOP -> chessPiece = ctx.deserialize(el, Bishop.class);
case QUEEN -> chessPiece = ctx.deserialize(el, Queen.class);
case KING -> chessPiece = ctx.deserialize(el, King.class);
}
}
return chessPiece;
});
return gsonBuilder.create();
}
Remember that you only need to use a GsonBuilder to override the default Gson serialization functionality if you want different classes to be used when you deserialize your JSON back into Java objects. This is usually because you are using interfaces, abstract classes, or derived classes in fields of your serialized object.
There are three main ways to make the Gson library available to your project:
-
Add the dependency from Intellij's File / Project Structure dialog
Search for gson and select the latest version
-
Create a Maven project and add the Maven dependency to your pom.xml file
com.google.code.gson gson 2.10.1 -
Create a Gradle project and add the Gradle dependency to your build.gradle file
implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1'
- How to read / understand JSON documents
- How to generate an JSON string from the instance variables of a Java object
- How to parse an JSON object and represent the data with a Java object
- Different ways to generate and parse JSON objects and the pros and cons of each
- How to use the Gson library to serialize and deserialize a Java object
- 🎥 JSON Introduction (8:44) - [transcript]
- 🎥 Parsing JSON (3:02) - [transcript]
- 🎥 JSON Stream Parser (14:09) - [transcript]
- 🎥 JSON DOM Parser(9:24) - [transcript]
- 🎥 JSON Object Serialization (12:50) - [transcript] Note: This video says that you will want to add dependencies in the Maven style. While the autograder will compile your Chess Project using Maven, loading the project in IntelliJ as a Maven project sometimes causes issues, so please use method #1 when adding dependencies to your project.
- 🎥 Creating GSON Project Dependency (6:06) - [transcript]
- 🎥 GSON Type Adapters (6:01) - [transcript]
- 🎥 Complex Type Adapters (11:59) - [transcript]
📁 domain
📁 parser