JSON Libraries Comparison Followup
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:
- Unity3D 5.4.0f3
- LitJSON 0.9.0
- Json.NET 9.0.1
- Full Serializer 1.1.0
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 |
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!
#1 by Ed Earl on September 19th, 2016 ·
Great article – thanks! A garbage comparison would be interesting, too. Last time I tried it, Json.NET made a LOT of heap allocations.
#2 by jackson on September 19th, 2016 ·
Interesting that you should mention garbage creation as someone else commented on the original article with the same request. :)
It’s an excellent suggestion for another followup article. Thanks!
#3 by NotAnotherGuy on October 9th, 2024 ·
I personally use Jsonifier – though it’s C++, it appears to currently be the highest performing out there. https://github.com/RealTimeChris/Jsonifier