Unity 5.5 has been out for about a month now and it’s time to update the benchmarks for JSON libraries. Which is fastest now? Which creates the least garbage? Read on to find out!

Last time I ran these benchmarks it was clear that Unity 5.4 was the fastest JSON library available. LitJSON and Json.NET were more or less tied for second place and FullSerializer was by far the slowest. I also tested how much garbage they created and found that, once again, Unity 5.4 created the least. Here too LitJSON and Json.NET tied for second place and FullSerializer brought up the rear.

Today’s test uses the latest Unity 5.5.0f3 release instead of 5.4.0f3 and also uses the latest source of FullSerializer, still at version 1.1.0. LitJSON and Json.NET remain unchanged at versions 0.9.0 and 9.0.1 respectively.

All the JSON libraries are put to the same test, which is a combination of the benchmarks and garbage creation tests from the previous two articles. If the profiler is running, the garbage tests run. Otherwise the benchmarks run.

using System;
using System.Collections.Generic;
using System.Text;
 
using UnityEngine;
using UnityEngine.Profiling;
 
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 fsSerializer = new fsSerializer();
		fsData fsData;
		fsSerializer.TrySerialize(saveGame, out fsData);
		fsJsonPrinter.CompressedJson(fsData);
 
		if (Profiler.enabled)
		{
			var tempSaveGame = new SaveGame();
			TestGarbage(saveGame, tempSaveGame, fsSerializer);
		}
		else
		{
			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)
			{
				fsSerializer.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);
				fsSerializer.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);
	}
 
	void TestGarbage(SaveGame saveGame, SaveGame tempSaveGame, fsSerializer fsSerializer)
	{
		var json = TestUnitySerialize(saveGame);
		TestUnityDeserialize(json);
 
		json = TestLitJsonSerialize(saveGame);
		TestLitJsonDeserialize(json);
 
		json = TestJsonDotNetSerialize(saveGame);
		TestJsonDotNetDeserialize(json);
 
		json = TestFullSerializerSerialize(saveGame, fsSerializer);
		TestFullSerializerDeserialize(json, fsSerializer, tempSaveGame);
	}
 
	string TestUnitySerialize(SaveGame saveGame)
	{
		return JsonUtility.ToJson(saveGame);
	}
 
	SaveGame TestUnityDeserialize(string json)
	{
		return JsonUtility.FromJson<SaveGame>(json);
	}
 
	string TestLitJsonSerialize(SaveGame saveGame)
	{
		return JsonMapper.ToJson(saveGame);
	}
 
	SaveGame TestLitJsonDeserialize(string json)
	{
		return JsonMapper.ToObject<SaveGame>(json);
	}
 
	string TestJsonDotNetSerialize(SaveGame saveGame)
	{
		return JsonConvert.SerializeObject(saveGame);
	}
 
	SaveGame TestJsonDotNetDeserialize(string json)
	{
		return JsonConvert.DeserializeObject<SaveGame>(json);
	}
 
	string TestFullSerializerSerialize(SaveGame saveGame, fsSerializer serializer)
	{
		fsData fsData;
		serializer.TrySerialize(saveGame, out fsData);
		return fsJsonPrinter.CompressedJson(fsData);
	}
 
	SaveGame TestFullSerializerDeserialize(string json, fsSerializer serializer, SaveGame saveGame)
	{
		var fsData = fsJsonParser.Parse(json);
		serializer.TryDeserialize<SaveGame>(fsData, ref saveGame);
		return saveGame;
	}
}

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.12.1
  • Unity 5.5.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 57 74
LitJSON 101 67 225
Json.NET 101 89 137
FullSerializer 101 299 797

JSON Library Performance

Little has changed since the last releases of Unity and FullSerializer. Unity seems a bit quicker, but it’s possibly just noise in the testing. The performance order of the libraries hasn’t changed and that’s the most important part.

I’ll omit the detailed output size data since it’s the same as before: all of the libraries output the same, minimal JSON.

Now let’s look at garbage creation. Unity only shows garbage in KB with one decimal place of precision, so some of these numbers are approximated by converting to bytes.

JSON Library Garbage Creation

Unity, LitJSON, and Json.NET are all exactly the same as before. FullSerializer does better at serialization now as it uses only 4.2 KB instead of the previous 4.4 KB. Unfortunately, it now does worse at deserialization by using 36.7 KB instead of 34 KB. Regardless, it still creates way more garbage than any of the other libraries.

So it turns out that not much has changed with JSON libraries since Unity 5.4 debuted. Their speed, output size, and garbage creation are all very much the same as before. Unity’s JsonUtility is still fastest and creates the least garbage with LitJSON and Json.NET coming in second place and FullSerializer being far behind those. If you can live with the restrictions imposed by JsonUtility (e.g. no support for Dictionary), then it’s probably the highest performance option of all the publicly-available libraries.

Which JSON library do you use? Why? Let me know in the comments!