Unity Dictionary Serialization
This post is pretty technical, so it will be most interesting to you if you're a Unity programmer...or just feeling a little adventurous.
I’m in the middle of making some big changes to our art pipeline to allow us more control over the way the art looks from inside Unity. One of the things I need to do is store a Dictionary of data inside a ScriptableObject. However, Unity doesn’t automatically serialize Dictionaries.
The result is that if you have a ScriptableObject or MonoBehaviour that contains a Dictionary field, when you save it to disk and reload it, that data is gone. This is not usually want you want…
The problem is actually a little bit more complicated than I described above. The data I actually need to serialize is the following:
Dictionary<Mesh, List<List<int>>>
And we’ll see why that complicates things in the solution section next…
(I need to thank Matt for pointing me to most of the resources I’m going to mention. This is why it’s awesome working with someone else: they can help you when you get stuck)
As I mentioned above, the first problem is that Unity just won’t serialize a Dictionary. In order to work around this, you need to create a derived class from Dictionary and write your own serialization callbacks. Matt pointed me to this link, which describes the code:
http://answers.unity3d.com/questions/460727/how-to-serialize-dictionary-with-unity-serializati.html
The end result is a class called SerializableDictionary, which looks like this:
using UnityEngine; using UnityEditor; using System; using System.Collections; using System.Collections.Generic; // Unity can't serialize Dictionaries, so this works around it by using Lists instead. // Details here: http://answers.unity3d.com/questions/460727/how-to-serialize-dictionary-with-unity-serializati.html // NOTE: The TKey and TValue types you pass in MUST be seriliable themselves. If they aren't // try using the generic derived class technique found at the same link. [Serializable] public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver { [SerializeField] private List<TKey> keys = new List<TKey>(); [SerializeField] private List<TValue> values = new List<TValue>(); // save the dictionary to lists public void OnBeforeSerialize() { keys.Clear(); values.Clear(); foreach(KeyValuePair<TKey, TValue> pair in this) { keys.Add(pair.Key); values.Add(pair.Value); } } // load dictionary from lists public void OnAfterDeserialize() { this.Clear(); if(keys.Count != values.Count) throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable.", keys.Count, values.Count)); for(int i = 0; i < keys.Count; i++) this.Add(keys[i], values[i]); } }
Great! Oh…except now we’ve run into a new problem. If you declare this field:
SerializableDictionary<Mesh, List<List<int>>>
it still won’t serialize because Unity doesn’t know how to serialize a generalized class (i.e. a class declared like MyClass<T>).
However, if you visit that same link from above:
http://answers.unity3d.com/questions/460727/how-to-serialize-dictionary-with-unity-serializati.html
We learn that you can derive a subclass of a generic class with a specific type and Unity can serialize that. So now we have this:
[Serializable] public class MeshToIntListListDict : SerializableDictionary<Mesh, List<List<int>>> {}
Awesome! Except…it still doesn’t serialize.
Now the problem is the type we’re trying to use for our Dictionary values. Our SerializableDictionary class requires that the type we pass as values to the Dictionary is serializable. This is because the SD class grabs all the keys and shoves them into a List, and it does the same with the values. So internally, it’s trying to serialize:
and Unity just goes. “Uh…” But there’s a way we can make this work:
http://answers.unity3d.com/questions/289692/serialize-nested-lists.html
All we have to do is create a serializable class that contains our List. And we can do this multiple times for as many nested lists as we need. So now we have this code:
[Serializable] public class IntList { public List<int> list = new List<int>(); } [Serializable] public class IntListList { public List<IntList> list = new List<IntList>(); } [Serializable] public class MeshToIntListListDict : SerializableDictionary<Mesh, IntListList> {} public MeshToIntListListDict myMeshDict = new MeshToIntListListDict();
And internal to the SerializableDictionary, it’s now creating:
which is fully serializable! Success! Now Unity can serialize our ridiculous data structure!
But wait, the major problem with this approach is that it makes the resulting code pretty ugly. Let’s say we want to add an element to our data structure. In the original case, we end up with this syntax:
myMeshDict[mesh][index].Add(curInt);
But now we end up with this mess:
myMeshDict[mesh].list[index].list.Add(curInt);
While that may not look overly confusing at first, I can assure you it is confusing to try to write. But we can fix that too with operator overloading. So let’s add array accessors and helper functions to our custom classes:
[Serializable] public class IntList { public List<int> list = new List<int>(); public int this[int i] { get { return list[i]; } set { list[i] = value; } } public void Add(int i) { list.Add(i); } public int Count { get { return list.Count; } } } [Serializable] public class IntListList { public List<IntList> list = new List<IntList>(); public IntList this[int i] { get { return list[i]; } set { list[i] = value; } } public void Add(IntList i) { list.Add(i); } public int Count { get { return list.Count; } } }
And now we can access our stuff in a nice way again:
myMeshDict[mesh][index].Add(curInt);