From The Archives: De/Serializing Top Level Arrays in JSON with Unity

From the archives celebrates the good and bad of my journey in software development. It's a chance to dredge up hundreds of hours of older work. Some I am proud of, some not. But it's with self reflection that we get to understand how far we have come.


The Problem (& Solution) Circa. 2016

Well it has been a while since my last tutorial. I’ve been experimenting with video tutorials and working on my game and also doing some paid project work at the same time. At least I am not bored!

That being said I like to note something down when I find something new or tricky that I get stuck on and today’s is simple but effective. For those that don’t know Unity actually has a nifty built in tool to serialize and de-serialize objects through the JsonUtility class. This is super useful for creating more human-readable data quickly and then adding it into your game. There is only one tiny problem for me, it doesn’t handle top level arrays. By that I mean the following won’t work.

[{"myvar";"myvalue"},{"myvar":"myothervalue"}]

I’m not sure the reasoning but my searches online did reveal from Unity staff that they currently do not support this feature. The easiest way to fix this is just wrap the array in a top level object like below.

{[{"myvar";"myvalue"},{"myvar":"myothervalue"}]}

It is a simple fix but it is annoying if you want to serialize or deserialize straight to (or from) an array, which is what I needed to do. So let’s look at making a JsonHelper class that fixes this issue for us by wrapping up top level arrays in a Wrapper class.

using System;

public static class JsonHelper {

    public static T[] FromJson<T>(string json) {
        Wrapper<T> wrapper = UnityEngine.JsonUtility.FromJson<Wrapper<T>>(json);
        return wrapper.Items;
    }

    public static string ToJson<T>(T[] array) {
        Wrapper<T> wrapper = new Wrapper<T>();
        wrapper.Items = array;
        return UnityEngine.JsonUtility.ToJson(wrapper);
    }

    [Serializable]
    private class Wrapper<T> {
        public T[] Items;
    }
}

The above code provides a simple black box to allow you to serialize or de-serialize those top level arrays but how does it work? The Wrapper is a [Serializable] class that is essentially empty, in fact it only holds a generic array. The generic array is for all intents and purposes our top level array. The thing we actually want to work with.

When de-serializing an array from Json it simply loads the Wrapper class, ignores the wrapper itself and returns the array. Conversely when serializing into Json we create a wrapper so that we have something to hold our array in. We then assign the array passed in to our function as the wrapper’s local variable. Lastly we use the built in JsonUtility to return the string back for writing.

There is now only one slight change to our Json file, the array is actually named therefore we should tweak it to suit. In any situations where you want to use a top level array instead of using our first example instead wrap it in an object and label the array “Items” as shown below.

{"Items":[{"myvar";"myvalue"},{"myvar":"myothervalue"}]}

NOTE: This only applies if you are ever manually creating the json you want to de-serialize. If for some reason you’re only ever going to be loading objects you’ve previously saved the JsonUtility does this for you.

Easy? You bet, but that was the whole point!


This "From the Archives" was super interesting because I actually had to look this one for one of my own personal game "Project Zap" only this week. In the original article I mentioned that

I’m not sure the reasoning...(behind why it behaves this way)

I guess technically I still don't know why, obviously that is just their implementation but the Unity documentation probably listed this constraint all along. It's funny how something I tackled so long ago came to front of mind so quickly simply because I took the effort to write it down in the past.

I still don't love this solution, having to set a top level array to an arbitrary key feels fraught with danger to me. Though it has proven effective for me on the projects in which I've used it because I can easily control the data format on import. Also it made for a super simple copy & paste solution for my current project!

It's strange to me that this requirement still exists and a more feature rich solution doesn't exist out-of-the-box, though I assume Newtonsoft JSON or something else could be leveraged if you have more robust JSON parsing requirements.

Anyway, here is to remembering to write things down 👍