Difficulty: Medium
My previous JSON tutorial was a while ago, and it never covered actually deserializing data (but is still useful on it’s own), so rather than doing the second half on its own, this tutorial will cover all the steps from the beginning using JsonFx.
∗ This guide is tested as of Unity 4.3.4f
Purpose
The purpose of serializing data is so that it can be stored or shared across different systems, or even applications. It creates a common data template that can be converted back and forth (serialized and deserialized) even when the source data is not one that is understood by the receiving system or application natively. There are various common formats that can be used (i.e XML, CSV, Binary, or in our case, JSON), to serialize data into.
In our example, we’re going to create a class that lives within our application; a Sandwich. When we create an instance of it, it will exist in memory until we destroy it or the app is stopped. Once we turn off play mode in the unity editor (or close our window in a build), that data is gone. By serializing it, in this case to a text file using JSON, not only can we store it to the file system, but we can edit it offline and see the changes reflected in our application when we load it up again. This is all sort of odd-sounding without seeing it in action, so let’s get to it.
Download the JsonFx DLL from here
Step 1: Creating the Container
The container is a class used to store the data you’re using in memory. This is not the only way to do it, but since you’re reading my guide, you’re stuck with my method (hah!). For my containers, I use straight C# classes. So, for our sandwich, we have:
Sandwich.cs
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class Sandwich{
public string name;
public string bread;
public float price;
public List<string> ingredients = new List<string>();
}
Just note that the fields are all public, this is important! Also, [System.Serializable] does 2 things for us: it allows JsonFx to serialize the fields, and it also exposes these fields to the inspector in Unity.
Pretty simple, right? Alright, let’s move on.
Step 2: Serializing (Saving/Writing) Data
I’ll start with the code, and then I’ll explain what’s what.
JsonTutorial.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pathfinding.Serialization.JsonFx;
using System.IO;
public class JsonTutorial : MonoBehaviour {
public string fileName;
public Sandwich sandwich;
private string PATH;
private void Start() {
PATH = Application.dataPath + "/../testData/";
}
private void OnGUI() {
if (GUILayout.Button("SAVE")){
SerializeAndSave();
}
}
private void SerializeAndSave() {
string data = JsonWriter.Serialize(sandwich);
if(!Directory.Exists(PATH)){
Directory.CreateDirectory(PATH);
}
var streamWriter = new StreamWriter(PATH + fileName + ".txt");
streamWriter.Write(data);
streamWriter.Close();
}
}
First thing to notice is that I included the JsonFx dll like so:
using Pathfinding.Serialization.JsonFx;
That particular namespace is unique to that download link I posted. If you were able to find another version of the dll, you’d include it by the appropriate namespace (i.e. JsonFx.Json).
Second, we include System.IO. This is because we’ll be reading and writing files.
It’s outside of the scope of this guide to explain the simple bits with the one line of OnGUI code, so we’ll skip that for now. How you choose to implement this info is up to you, but for the sake of demonstrating the concepts, I’ll simply fill out the fields via the inspector. You can fill these in any way you like, but here’s what I used, so that you can see the output later on.
The file name is just that. There is no need to give it an extension, since as you’ll notice in the SerializeAndSave() method, we give it one automatically (though you can feel free to modify the code to do it).
The next few fields are just the ones we defined earlier in the container. Once again, simple, right?
Ok, here’s the voodoo in SerializeAndWrite():
string data = JsonWriter.Serialize(sandwich);
That one line right there handles the serialization of our sandwich object to a text file. JsonFx handles the heavy lifting for us. You could Debug.Log(data) to see what it looks like, but we’re going to write it to a text file anyway, so let’s continue.
The next few lines simply check if the directory we specified in PATH exists, and creates it if not. Note that by default, Application.dataPath will give your /Assets directory. I’ve had issues writing to it, so I just go up one directory (/..) and save to a directory in my MyProject/ folder (so, next to /Assets, basically).
The StreamWriter constructor actually takes the whole path including the file name. Again, you could simply omit manually adding the “.txt” extension, and leaving it up to the user to fill it in the fileName variable, but whatevs. You’d be able to open the file in a text editor even if you didn’t give it an extension.
Testing It!
Simply drop the JsonTutorial script as a component in any scene, and you’ll see a “SAVE” button (remember that OnGUI thing we skipped?). Click the button, and check your project folder. You should see a /testData directory in there and inside it, a file named whatever you set in the inspector (my_data.txt in my case).
based on the sandwich I designed, the file will contain the following JSON as text (which I formatted using this utility to look pretty):
{ "name": "meatball", "bread": "white", "price": 5.99, "ingredients": [ "metaballs", "sauce", "cheese" ] }
We could even switch things up a bit by providing it a list of sandwiches instead of just one. Let’s create a simple container to hold a list of sandwiches (required for this to work, since you can’t deserialize a List<Sandwiches> directly.
In Sandwich.cs
[System.Serializable]
public class Sandwiches{
public List<Sandwich> sandwiches = new List<Sandwich>();
}
Instead, of your Sandwich sandwich variable, create a new filed for Sandwiches sandwiches which in turn contains a List<Sandwich> as per our container.
Just remember to change JsonWriter.Serialize(sandwich); to JsonWriter.Serialize(sandwiches); add some sandwiches in the inspector, run it, and see what you get.
Step 3: Loading and Deserializing
The hard part is done, we already have everything we need, so all that’s left is to create a method to load the data, and a button to call that method.
Here’s our method:
In JsonTutorial.cs
private void LoadAndDeserialize(){
var streamReader = new StreamReader(PATH + fileName + ".txt");
string data = streamReader.ReadToEnd();
streamReader.Close();
sandwiches = JsonReader.Deserialize<Sandwiches>(data);
}
It looks similar to our SerializeAndSave() method, but we’re doing the opposite here (duh) by reading.
Instead of a StreamWriter, we are using a StreamReader
and instead of a JsonWriter.Serialize, we are using JsonReader.Deserialize. You’ll notice the syntax is a bit different here, since our Reader needs to know what type to deserialize into (in this case, Sandwiches), and you pass it the data to deserialize, which is the string we got back from our StreamReader.
Step 4: Testing It… Again!
If you got lost somewhere along the way, I posted the full code below, for reference (or for cheating… don’t think I’m not on to you).
Alright, so make sure you added a LOAD button to call our LoadAndDeserialize() method. Now, go to your Sandwiches object in the inspector and clear it out. The data is gone! But no worries, we’ll just load it up from the test file we created earlier. Click your button, and you’ll see the fields populate with the data we’ve loaded.
You can tweak the contents of the text file (without changing the JSON structure of course) all you want, change names, ingredients, prices, etc.
And that’s it for the basics! You can experiment with this and look into saving your own data in your projects. Feel free to shoot any questions to @ray_barrera or in the comments.
Final Code
JsonTutorial.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pathfinding.Serialization.JsonFx;
using System.IO;
public class JsonTutorial : MonoBehaviour {
public string fileName;
public Sandwiches sandwiches = new Sandwiches();
//public List<Sandwich> sandwiches = new List<Sandwich>();
private string PATH;
private void Start() {
PATH = Application.dataPath + "/../testData/";
}
private void OnGUI() {
if (GUILayout.Button("SAVE")){
SerializeAndSave();
}
if (GUILayout.Button("LOAD")){
LoadAndDeserialize();
}
}
private void SerializeAndSave() {
string data = JsonWriter.Serialize(sandwiches);
if(!Directory.Exists(PATH)){
Directory.CreateDirectory(PATH);
}
var streamWriter = new StreamWriter(PATH + fileName + ".txt");
streamWriter.Write(data);
streamWriter.Close();
}
private void LoadAndDeserialize(){
var streamReader = new StreamReader(PATH + fileName + ".txt");
string data = streamReader.ReadToEnd();
streamReader.Close();
sandwiches = JsonReader.Deserialize<Sandwiches>(data);
}
}
Sandwich.cs
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class Sandwich{
public string name;
public string bread;
public float price;
public List<string> ingredients = new List<string>();
public Sandwich() { }
}
[System.Serializable]
public class Sandwiches{
public List<Sandwich> sandwiches = new List<Sandwich>();
}