JSON serialization and deserialization

JSON stands for JavaScript Object Notation and it is an open standard, human readable format to transmit objects formed by attribute-value pairs. It has become very popular to transmit data between clients and servers, but it is also heavily used in games as an alternative of XML.

Originally, it came from JavaScript, although it is actually language agnostic as there are many utilities to write and read JSON in virtually every widely used language. Being less verbose than XML and easier to parse are probably its main advantages when compared to the aforementioned format.

JSON is really awesome, but in order to be able to follow this recipe, make sure you are familiar with the basics. Check out http://www.json.org for more details.

In this recipe, you will learn how to deserialize (read) Java objects from JSON files and serialize them back to JSON.

Getting ready

Make sure you have the sample projects in your Eclipse workspace.

How to do it…

The code for this recipe can be found in the JSONParsingSample class. We will work with the data/character.json file, which defines an RPG-like character with some typical attributes and his inventory:

{
  name: "David",
  experience: 2534,
  strength: 6,
  dexterity: 8,
  intelligence: 6,
  inventory : [{ name: "iron-sword", number: 1 },
    { name: "wooden-shield", number: 1 },
    { name: "leather-armor", number: 1 },
    { name: "leather-boots", number: 1 },
    { name: "healing-potion", number: 3 },
    { name: "poison-herbs", number: 2 }]
}

The two following classes will model our character in code:

public class Item {
  private String name;
  private int number;
}
public class Character {
  private String name = "";
  private int experience = 0;
  private int strength = 1;
  private int dexterity = 1;
  private int intelligence = 1;
  private Array<Item> inventory = new Array<Item>();
}

Now that we have the basics, it is time to learn how to deal with the Libgdx JSON API. We will start with reading, then proceed to writing, and finally, we will cover some advanced aspects.

Reading objects from JSON

The Json class can recreate objects automatically from a JSON-formatted string:

Json json = new Json();

It associates the keys in the file with member names in the corresponding class using reflection. This works wonderfully for basic data types, but it gets slightly more complicated when objects contain other objects. In our case, we can have a problem with the items array. There are two ways to handle this:

  • The item entries in the file should say which class they belong to:
    { class: com.cookbook.samples.JSONParsinSample.Item: "wooden-shield", number: 1 }
  • We tell the Json object that the "inventory" element inside Character contains objects of type Array<Item>:
    json.setElementType(Character.class, "inventory", Array<Item>.class);

Once we have cleared out these potential conflicts, we can safely instantiate a new Character object from the file:

Character character = json.fromJson(Character.class, Gdx.files.internal("data/character.json"));

Magic! We managed to create a Character instance from the JSON file with very little hassle.

Writing objects to JSON

We can now work with our Character object, increase the experience level, add a few items, and modify some of the attributes. The player might reach a save point, so we need to serialize character and save it off to disk. Luckily enough, this is simple. Consider the following code:

FileHandle handle = Gdx.files.external("character.json");
String string = json.toJson(character);
string = json.prettyPrint(string);
handle.writeString(string);

The toJson() method produces a compact but not very readable result. We call prettyPrint() on the result so as to get proper indentation.

Manual JSON parsing

Sometimes, you might need to do manual parsing for some objects in your game. Imagine you have an enemy.json file that defines all the attributes for an enemy in your game and uses a string to identify the texture the character should use. The Enemy class will have a Texture member, but it will just be a string in the file. In this case, you will have to read the texture name and instantiate a Texture object with the result.

First, you need to parse the file and get the JsonValue root object:

JsonReader reader = new JsonReader();
JsonValue root = reader.parse(Gdx.files.internal("data/enemy.json");

The JsonValue class models elements in a document, which can be of different types such as string, integer, Boolean, float, array, or object. Checking the value type is simple; call one of the following methods: isArray(), isBoolean(), isNumber(), isString(), isObject(), or isNull().

To retrieve the contents of a JsonValue object, you can use one of the following: asBoolean(), asFloat(), asInt(), asArray(), or asString().

When JsonValue instances are objects, they might have children that you can easily retrieve. Call has(), passing in the name to make sure a child of the given name exists. Once you are sure, use one of the following, simple that defaultValue can be omitted:

JsonValue get(String name)
String getString(String name, String defaultValue)
bool getBoolean(String name, bool defaultValue)
float getFloat(String name, float defaultValue)
int getInt(String name, int defaultValue)

Iterating over an array element is easy with the JsonIterator class:

for (JsonValue item : items) {
  …
}

This will give you enormous flexibility when it comes to parsing JSON documents.

How it works…

Libgdx's JSON implementation does not strictly follow the standard; it is a bit more forgiving. Firstly, value names do not need to be quoted. Officially, it should be "name": "David" rather than name: "David". Secondly, it allows C-style comments in case developers want to document complicated formats inline.

There's more…

The Libgdx JSON library is rather powerful and has a few extra goodies to help you with serialization.

The Serializable interface

When an object is particularly complicated, such as our previous Enemy example, you might want to make it implement the Serializable interface so that you can customize the process:

public class Enemy implements Serializable {
  public void read(Json json, JsonValue jsonData) { ... }
  public void write(Json json) { ... }
}

The Serializer interface

Making your classes responsible for their own serialization might be seen as polluting them. Do not panic though, you can still customize serialization without touching the serializable class with a different class that implements the Serializer interface:

public class EnemySerializer implements Serializer<Enemy> {
  public Enemy read(Json json, JsonValue jsonData, Class type) { ... }
  public void write(Json json, Enemy object, Class knownType) { ... }
}

Json json = new Json();
json.setSerializer(Enemy.class, new EnemySerializer());

See also

  • We have covered all Libgdx features related to file handling, and you should have a pretty clear understanding of how to introduce save and configuration files in your project. This will be the time to revisit areas that you did not understand very well. Otherwise, move on to Chapter 6, Font Rendering.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.164.34