I wrote an article when Unity 5.3 came out to test its built-in JSON serializer library against some of the open source JSON libraries. Today’s article updates with Unity 5.4 and adds a requested JSON library—Full Serializer—to the mix. Has Unity 5.4 improved performance? Is the new version of JSON.NET any faster? Can Full Serializer best them all? Read on to find out!

Today’s contenders are:

As with last time, we’ll be serializing this simple “save game” structure:

[Serializable]
public class SaveGame
{
	public string Name;
	public int HighScore;
	public List<InventoryItem> Inventory;
}
 
[Serializable]
public class InventoryItem
{
	public int Id;
	public int Quantity;
}

Each library will be judged on these grounds:

  • JSON size
  • Serialization time
  • Deserialization time

There’s also the hard-to-compare “ease of use” factor. Each is about the same, but Full Serializer takes a little more work. You could argue that it gains some flexibility though.

using UnityEngine;
var unityJson = JsonUtility.ToJson(saveGame);
var unitySaveGame = JsonUtility.FromJson<SaveGame>(unityJson);
 
using LitJson;
var litJsonJson = JsonMapper.ToJson(saveGame);
var litJsonSaveGame = JsonMapper.ToObject<SaveGame>(litJsonJson);
 
using Newtonsoft.Json;
var jsonDotNetJson = JsonConvert.SerializeObject(saveGame);
var jsonDotNetSaveGame = JsonConvert.DeserializeObject<SaveGame>(jsonDotNetJson);
 
using FullSerializer;
fsData fsData;
fullSerializerSerializer.TrySerialize(saveGame, out fsData);
var fullSerializerJson = fsJsonPrinter.CompressedJson(fsData);
fsData = fsJsonParser.Parse(fullSerializerJson);
fullSerializerSerializer.TryDeserialize<SaveGame>(fsData, ref fullSerializerSaveGame);

Now for the size and performance test script:

using System;
using System.Collections.Generic;
using System.Text;
 
using UnityEngine;
 
using LitJson;
using Newtonsoft.Json;
using FullSerializer;
 
[Serializable]
public class SaveGame
{
	public string Name;
	public int HighScore;
	public List<InventoryItem> Inventory;
}
 
[Serializable]
public class InventoryItem
{
	public int Id;
	public int Quantity;
}
 
class TestScript : MonoBehaviour
{
	string report = "";
 
	void Start()
	{
		var saveGame = new SaveGame
			{
				Name = "Speed Run",
				HighScore = 10000,
				Inventory = new List<InventoryItem> {
					new InventoryItem {
						Id = 100,
						Quantity = 5
					},
					new InventoryItem {
						Id = 200,
						Quantity = 20
					}
				}
			};
 
		// Warm up reflection
		JsonUtility.ToJson(saveGame);
		JsonMapper.ToJson(saveGame);
		JsonConvert.SerializeObject(saveGame);
		var fullSerializerSerializer = new fsSerializer();
		fsData fsData;
		fullSerializerSerializer.TrySerialize(saveGame, out fsData);
		fsJsonPrinter.CompressedJson(fsData);
 
		var stopwatch = new System.Diagnostics.Stopwatch();
		const int reps = 10000;
		string unityJson = null;
		string litJsonJson = null;
		string jsonDotNetJson = null;
		string fullSerializerJson = null;
		SaveGame unitySaveGame = null;
		SaveGame litJsonSaveGame = null;
		SaveGame jsonDotNetSaveGame = null;
		SaveGame fullSerializerSaveGame = null;
 
		stopwatch.Start();
		for (var i = 0; i < reps; ++i)
		{
			unityJson = JsonUtility.ToJson(saveGame);
		}
		var unitySerializeTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < reps; ++i)
		{
			litJsonJson = JsonMapper.ToJson(saveGame);
		}
		var litJsonSerializeTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < reps; ++i)
		{
			jsonDotNetJson = JsonConvert.SerializeObject(saveGame);
		}
		var jsonDotNetSerializeTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < reps; ++i)
		{
			fullSerializerSerializer.TrySerialize(saveGame, out fsData);
			fullSerializerJson = fsJsonPrinter.CompressedJson(fsData);
		}
		var fullSerializerSerializeTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < reps; ++i)
		{
			unitySaveGame = JsonUtility.FromJson<SaveGame>(unityJson);
		}
		var unityDeserializeTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < reps; ++i)
		{
			litJsonSaveGame = JsonMapper.ToObject<SaveGame>(litJsonJson);
		}
		var litJsonDeserializeTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < reps; ++i)
		{
			jsonDotNetSaveGame = JsonConvert.DeserializeObject<SaveGame>(jsonDotNetJson);
		}
		var jsonDotNetDeserializeTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < reps; ++i)
		{
			fsData = fsJsonParser.Parse(fullSerializerJson);
			fullSerializerSerializer.TryDeserialize<SaveGame>(fsData, ref fullSerializerSaveGame);
		}
		var fullSerializerDeserializeTime = stopwatch.ElapsedMilliseconds;
 
		report += "Unity: " + unityJson + "\n";
		report += "LitJSON: " + litJsonJson + "\n";
		report += "Json.NET: " + jsonDotNetJson + "\n";
		report += "FullSerializer: " + fullSerializerJson + "\n";
		PrintSaveGame("Unity", unitySaveGame);
		PrintSaveGame("LitJSON", litJsonSaveGame);
		PrintSaveGame("Json.NET", jsonDotNetSaveGame);
		PrintSaveGame("FullSerializer", fullSerializerSaveGame);
 
		var unitySize = Encoding.UTF8.GetBytes(unityJson).Length;
		var litJsonSize = Encoding.UTF8.GetBytes(litJsonJson).Length;
		var jsonDotNetSize = Encoding.UTF8.GetBytes(jsonDotNetJson).Length;
		var fullSerializerSize = Encoding.UTF8.GetBytes(fullSerializerJson).Length;
		report += string.Format(
			"Library,Size,SerializeTime,Deserialize Time\n" +
			"Unity,{0},{1},{2}\n" +
			"LitJSON,{3},{4},{5}\n" +
			"Json.NET,{6},{7},{8}\n" +
			"FullSerializer,{9},{10},{11}\n",
			unitySize, unitySerializeTime, unityDeserializeTime,
			litJsonSize, litJsonSerializeTime, litJsonDeserializeTime,
			jsonDotNetSize, jsonDotNetSerializeTime, jsonDotNetDeserializeTime,
			fullSerializerSize, fullSerializerSerializeTime, fullSerializerDeserializeTime
		);
	}
 
	void PrintSaveGame(string title, SaveGame saveGame)
	{
		var builder = new StringBuilder(title);
		builder.Append(":\n\tName=");
		builder.Append(saveGame.Name);
		builder.Append('\n');
		builder.Append("\tHighScore=");
		builder.Append(saveGame.HighScore);
		builder.Append("\n");
		builder.Append("\tInventory=\n");
		foreach (var item in saveGame.Inventory)
		{
			builder.Append("\t\t");
			builder.Append("Id=");
			builder.Append(item.Id);
			builder.Append(", Quantity=");
			builder.Append(item.Quantity);
			builder.Append('\n');
		}
		report += builder.ToString() + "\n";
	}
 
	void OnGUI()
	{
		GUI.TextArea(new Rect(0, 0, Screen.width, Screen.height), report);
	}
}

If you want to try out the test yourself, simply paste the above code into a TestScript.cs file in your Unity project’s Assets directory and attach it to the main camera game object in a new, empty project. Then build in non-development mode for 64-bit processors and run it windowed at 640×480 with fastest graphics. I ran it that way on this machine:

  • 2.3 Ghz Intel Core i7-3615QM
  • Mac OS X 10.11.5
  • Unity 5.4.0f3, Mac OS X Standalone, x86_64, non-development
  • 640×480, Fastest, Windowed

And here are the results I got:

Library Size SerializeTime Deserialize Time
Unity 101 63 85
LitJSON 101 74 234
Json.NET 101 86 148
FullSerializer 101 307 787

JSON LIbrary Performance Graph

JSON LIbrary Size Graph

All three JSON serializers produced the exact same, minimal JSON:

Unity:          {"Name":"Speed Run","HighScore":10000,"Inventory":[{"Id":100,"Quantity":5},{"Id":200,"Quantity":20}]}
LitJSON:        {"Name":"Speed Run","HighScore":10000,"Inventory":[{"Id":100,"Quantity":5},{"Id":200,"Quantity":20}]}
Json.NET:       {"Name":"Speed Run","HighScore":10000,"Inventory":[{"Id":100,"Quantity":5},{"Id":200,"Quantity":20}]}
FullSerializer: {"Name":"Speed Run","HighScore":10000,"Inventory":[{"Id":100,"Quantity":5},{"Id":200,"Quantity":20}]}

They all deserialized to the exact same SaveGame instance:

Unity:
	Name=Speed Run
	HighScore=10000
	Inventory=
		Id=100, Quantity=5
		Id=200, Quantity=20
 
LitJSON:
	Name=Speed Run
	HighScore=10000
	Inventory=
		Id=100, Quantity=5
		Id=200, Quantity=20
 
Json.NET:
	Name=Speed Run
	HighScore=10000
	Inventory=
		Id=100, Quantity=5
		Id=200, Quantity=20
 
FullSerializer:
	Name=Speed Run
	HighScore=10000
	Inventory=
		Id=100, Quantity=5
		Id=200, Quantity=20

Serialization performance varied wildly though. Unity’s JsonUtility was fastest with LitJSON and Json.NET not far behind. Full Serializer, on the other hand, was 3.5x slower than the next-slowest Json.NET.

Deserialization performance was much the same. Unity’s JsonUtility was the quickest with Json.NET then LitJSON roughly 2x slower. Full Serializer was about 3.3x slower than the next-slowest LitJSON.

That said, it’s important to take these performance benchmarks with a grain of salt. Your JSON documents probably don’t look exactly like SaveGame, so you’ll end up with different performance numbers in the “real world” than in this article. For more varied documents put to the test, check out this expansive article. It’s likely that Full Serializer will always be the slowest of the bunch, so you should probably steer clear if you need high performance JSON serialization and deserialization.

What’s your JSON library of choice? Let me know in the comments!