Reading & Writing JSON - garsue/libgdx GitHub Wiki
- Overview
- Writing Object Graphs
- Reading Object Graphs
- Customizing Serialization
- Serialization Methods
- Event Based Parsing
libgdx can perform automatic object to JSON serialization and JSON to object deserialization. Four small classes make up the API:
-
JsonWriter
: A builder style API for emitting JSON. -
JsonReader
: Parses JSON and builds a DOM ofJsonValue
objects. -
JsonValue
: Describes a JSON object, array, string, float, long, boolean, or null. -
Json
: Reads and writes arbitrary object graphs usingJsonReader
andJsonWriter
.
To use these classes outside of libgdx, see the JsonBeans project.
The Json
class uses reflection to automatically serialize objects to JSON. For example, here are two classes (getters/setters and constructors omitted):
public class Person {
private String name;
private int age;
private ArrayList numbers;
}
public class PhoneNumber {
private String name;
private String number;
}
Example object graph using these classes:
Person person = new Person();
person.setName("Nate");
person.setAge(31);
ArrayList numbers = new ArrayList();
numbers.add(new PhoneNumber("Home", "206-555-1234"));
numbers.add(new PhoneNumber("Work", "425-555-4321"));
person.setNumbers(numbers);
The code to serialize this object graph:
Json json = new Json();
System.out.println(json.toJson(person));
{numbers:[{class:com.example.PhoneNumber,number:"206-555-1234",name:Home},{class:com.example.PhoneNumber,number:"425-555-4321",name:Work}],name:Nate,age:31}
That is compact, but hardly legible. The prettyPrint
method can be used:
Json json = new Json();
System.out.println(json.prettyPrint(person));
{
numbers: [
{
class: com.example.PhoneNumber,
number: "206-555-1234",
name: Home
},
{
class: com.example.PhoneNumber,
number: "425-555-4321",
name: Work
}
],
name: Nate,
age: 31
}
Note that the class for the PhoneNumber
objects in the ArrayList numbers
field appears in the JSON. This is required to recreate the object graph from the JSON because ArrayList
can hold any type of object. Class names are only output when they are required for deserialization. If the field was ArrayList<PhoneNumber> numbers
then class names would only appear when an item in the list extends PhoneNumber
. If you know the concrete type or aren't using generics, you can avoid class names being written by telling the Json
class the types:
Json json = new Json();
json.setElementType(Person.class, "numbers", PhoneNumber.class);
System.out.println(json.prettyPrint(person));
{
numbers: [
{
number: "206-555-1234",
name: Home
},
{
number: "425-555-4321",
name: Work
}
],
name: Nate,
age: 31
}
When writing the class cannot be avoided, an alias can be given:
Json json = new Json();
json.addClassTag("phoneNumber", PhoneNumber.class);
System.out.println(json.prettyPrint(person));
{
numbers: [
{
class: phoneNumber,
number: "206-555-1234",
name: Home
},
{
class: phoneNumber,
number: "425-555-4321",
name: Work
}
],
name: Nate,
age: 31
}
The Json
class can write and read both JSON and a couple JSON-like formats. It supports "JavaScript", where the object property names are only quoted when needed. It also supports a "minimal" format (the default), where both object property names and values are only quoted when needed.
Json json = new Json();
json.setOutputType(OutputType.json);
json.setElementType(Person.class, "numbers", PhoneNumber.class);
System.out.println(json.prettyPrint(person));
{
"numbers": [
{
"number": "206-555-1234",
"name": "Home"
},
{
"number": "425-555-4321",
"name": "Work"
}
],
"name": "Nate",
"age": 31
}
The Json
class uses reflection to automatically deserialize objects from JSON. Here is how to deserialize the JSON from the previous examples:
Json json = new Json();
String text = json.toJson(person);
Person person2 = json.fromJson(Person.class, text);
The type passed to fromJson
is the type of the root of the object graph. From this, the Json
class determines the types of all the fields and all other objects encountered, recursively. The "knownType" and "elementType" of the root can be passed to toJson
. This is useful if the type of the root object is not known:
Json json = new Json();
json.setOutputType(OutputType.minimal);
String text = json.toJson(person, Object.class);
System.out.println(json.prettyPrint(text));
Object person2 = json.fromJson(Object.class, text);
{
class: com.example.Person,
numbers: [
{
class: com.example.PhoneNumber,
number: "206-555-1234",
name: Home
},
{
class: com.example.PhoneNumber,
number: "425-555-4321",
name: Work
}
],
name: Nate,
age: 31
}
To read the JSON as a DOM of maps, arrays, and values, the JsonReader
class can be used:
Json json = new Json();
String text = json.toJson(person, Object.class);
JsonValue root = new JsonReader().parse(text);
The JsonValue
describes a JSON object, array, string, float, long, boolean, or null.
Usually automatic serialization is desired and there is no need to customize how specific classes are serialized. When needed, serialization can be customized by either having the class to be serialized implement the Json.Serializable
interface, or by registering a Json.Serializer
with the Json
instance.
This example uses Json.Serializable
to write a phone number as an object with a single field:
static public class PhoneNumber implements Json.Serializable {
private String name;
private String number;
public void write (Json json) {
json.writeValue(name, number);
}
public void read (Json json, JsonValue jsonMap) {
name = jsonMap.child().name();
number = jsonMap.child().asString();
}
}
Json json = new Json();
json.setElementType(Person.class, "numbers", PhoneNumber.class);
String text = json.prettyPrint(person);
System.out.println(text);
Person person2 = json.fromJson(Person.class, text);
{
numbers: [
{
Home: "206-555-1234"
},
{
Work: "425-555-4321"
}
],
name: Nate,
age: 31
}
The class implementing Json.Serializable
must have a zero argument constructor because object construction is done for you. In the write
method, the surrounding JSON object has already been written. The read
method always receives a JsonValue
that represents that JSON object.
Json.Serializer
provides more control over what is output, requiring writeObjectStart
and writeObjectEnd
to be called if you require a JSON object like Json.Serializable
. Alternatively, a JSON array or a simple value (string, int, boolean) could be output instead of an object. Json.Serializer
also allows the object creation to be customized:
Json json = new Json();
json.setSerializer(PhoneNumber.class, new Json.Serializer<PhoneNumber>() {
public void write (Json json, PhoneNumber number, Class knownType) {
json.writeObjectStart();
json.writeValue(number.name, number.number);
json.writeObjectEnd();
}
public PhoneNumber read (Json json, JsonValue jsonData, Class type) {
PhoneNumber number = new PhoneNumber();
number.setName(jsonData.child().name());
number.setNumber(jsonData.child().asString());
return number;
}
});
json.setElementType(Person.class, "numbers", PhoneNumber.class);
String text = json.prettyPrint(person);
System.out.println(text);
Person person2 = json.fromJson(Person.class, text);
Json
has many methods to read and write data to the JSON. Write methods without a name string are used to write a value that is not a JSON object field (eg, a string or an object in a JSON array). Write methods that take a name string are used to write a field name and value for a JSON object.
writeObjectStart
is used to start writing a JSON object, then values can be written using the write methods that take a name string. When the object is finished, writeObjectEnd
must be called:
json.writeObjectStart();
json.writeValue("name", "value");
json.writeObjectEnd();
The writeObjectStart
methods that take an actualType and a knownType will write a class field to the JSON if the types differ. This enables the actual type to be known during deserialization. For example, the known type may be java.util.Map but the actual type is java.util.LinkedHashMap (which extends HashMap), so deserialization needs to know the actual type to create.
Writing arrays works in a similar manner, except the values should be written using the write methods that do not take a name string:
json.writeArrayStart();
json.writeValue("value1");
json.writeValue("value2");
json.writeArrayEnd();
The Json
class can automatically write Java object fields and values. writeFields
writes all fields and values for the specified Java object to the current JSON object:
json.writeObjectStart();
json.writeFields(someObject);
json.writeObjectEnd();
The writeField
method writes the value for a single Java object field:
json.writeObjectStart();
json.writeField(someObject, "javaFieldName", "jsonFieldName");
json.writeObjectEnd();
Many of the write methods take an "element type" parameter. This is used to specify the known type of objects in a collection. For example, for a list:
ArrayList list = new ArrayList();
list.add(someObject1);
list.add(someObject2);
list.add(someObject3);
list.add(someOtherObject);
...
json.writeObjectStart();
json.writeValue("items", list);
json.writeObjectEnd();
{
items: [
{ class: com.example.SomeObject, value: 1 },
{ class: com.example.SomeObject, value: 2 },
{ class: com.example.SomeObject, value: 3 },
{ class: com.example.SomeOtherObject, value: four }
]
}
Here the known type of objects in the list is Object, so each object in the JSON for "items" has a class field that specifies Integer or String. By specifying the element type, Integer is used as the known type so only the last entry in the JSON for "items" has a class field:
json.writeObjectStart();
json.writeValue("items", list, ArrayList.class, Integer.class);
json.writeObjectEnd();
{
items: [
{ value: 1 },
{ value: 2 },
{ value: 3 },
{ class: com.example.SomeOtherObject, value: four }
]
}
For maps, the element type is used for the values. The keys for maps are always strings, a limitation of how object fields are described using JSON.
Note that the Json
class uses generics on Java field declarations to determine the element type where possible.
The JsonReader
class reads JSON and has protected methods that are called as JSON objects, arrays, strings, floats, longs, and booleans are encountered. By default, these methods build a DOM out of JsonValue
objects. These methods can be overridden to do your own event based JSON handling.