[Solved]How to serialize Dictionary with Unity Serialization System

Edit: http://forum.unity3d.com/threads/finally-a-serializable-dictionary-for-unity-extracted-from-system-collections-generic.335797/

Hello everyone,

I already read many things about Serialization with Unity and i already know that Dictionary are not serialized by Unity. But I need to use Dictionaries (or something similar) and to serialized data they contains.

I have tried something like this :

sing UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

[Serializable]
public class Map<TKey, TValue>{
	
	[SerializeField]
	private List<TKey> keysList = new List<TKey>();
	public List<TKey> KeysList
	{
		get{return keysList;}
		set{keysList=value;}
	}

	[SerializeField]
	private List<TValue> valuesList = new List<TValue>();
	public List<TValue> ValuesList
	{
		get{return valuesList;}
		set{valuesList=value;}
	}
	
	private Dictionary<TKey, TValue> dictionaryData = new Dictionary<TKey, TValue>();
	public Dictionary<TKey, TValue> DictionaryData
	{
		get{return dictionaryData;}
		set{dictionaryData =value;}
	}
	
	public void Awake()
	{
		try{
			
			for(int i =0; i<keysList.Count;i++)
			{
				dictionaryData.Add(keysList_, valuesList*);*_

* }*

* }*
* catch(Exception)*
* {*
* Debug.LogError(“KeysList.Count is not equal to ValuesList.Count. It shouldn’t happen!”);*
* }*

* }*

* public void OnEnable ()*
{

Debug.Log(“totototo”);

* }*

* public void Add(TKey key, TValue data)*
* {*
* dictionaryData.Add(key, data);*
* keysList.Add(key);*
* valuesList.Add(data);*
* }*

* public void Remove(TKey key)*
* { *
* valuesList.Remove(dictionaryData[key]);*
* keysList.Remove(key);*
* dictionaryData.Remove(key);*

* }*

* public bool ContainsKey(TKey key)*
* {*
* return DictionaryData.ContainsKey(key);*
* }*

* public bool ContainsValue(TValue data)*
* {*
* return DictionaryData.ContainsValue(data);*
* }*

* public void Clear()*
* {*
* DictionaryData.Clear();*
* keysList.Clear();*
* valuesList.Clear();*
* }*
The problem is that none of my variables are serialized, even the simple List and List. Maybe it’s because I don’t inherit from ScriptableObject.
But if I inherit from ScriptableObject, could I still use templates? I believe that ScriptableObject can’t instantiate a templated class, because I haven’t seen anything about this until now. So how can I serialize the data of my Dictionaries?
Can I use a binary serialization for this part? Using both serialization seems to be a little bit weird and unconstant. So any help could be really nice.
Thanks for reading.
Iwa

Although there already is an accepted answer, I would like to share solution that is better in my opinion. It uses the ISerializeCallback interface to implement the serialization functionality into a class derived from the Dictionary.

[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."));

		for(int i = 0; i < keys.Count; i++)
			this.Add(keys_, values*);*_

* }*
}
In order to work though, you still have to derive a non generic class from this one:
[Serializable] public class DictionaryOfStringAndInt : SerializableDictionary<string, int> {}
This derived class now works exactly like a standard dictionary, but is serializable. However, keep in mind that the key type and value type must be serializable themself.

Have you seen this question? If you have a generic type Foo

[Serializable]
public class Foo<T>
{
  public List<T> stuff;
}

And use it in the following way

public class Foobar : MonoBehaviour
{
  public Foo<int> baz;
}

The instance variable baz won’t be serializable through the Unity Editor. However, if you create a non-generic subclass of Foo, it will be serializable

public class Foobar : MonoBehaviour
{
  [Serializable]
  public class FooInt : Foo<int> { }
  public FooInt baz;
}

To be honest, I’m really not sure why this works, but I’ve used this hack on a number of occasions, to serialize generic types through the Editor. In your case, you would create a non-generic subclass of Map, for each combination of TKey and TValue that you plan on using, and then use the non-generic subclass in your code so that it can be serialized.

EDIT: Here’s a more solid solution, works with Unity’s default serialization http://forum.unity3d.com/threads/finally-a-serializable-dictionary-for-unity-extracted-from-system-collections-generic.335797/


Allow me to share my solution. Serializing and exposing dictionaries along with any generic type is what is offered in VFW - I make use of ISerializationCallbackReceiver and FullSerializer to write a better custom serialization system that could pretty much serialize anything (interfaces, generics, properties, etc)

If you still need to roll with Unity’s serialization, you could try implementing a dictionary with two serializable lists, while there’s no hashing, but ya know, it works.

Thanks for your answers, Iwa and hiddenspring81.

I also needed to serialize a dictionary, but I generally don’t like using parallel sets of information, so I made my own version of the same concept that uses a custom KeyValuePair class to keep both bits of information together. It implements the IDictionary interface for a little extra versatility.

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

public abstract class SerializedKeyValuePair<TKey, TValue>
	where TKey : IEquatable<TKey>
{
	public abstract TKey Key { get; set; }
	public abstract TValue Value { get; set; }
	
	public SerializedKeyValuePair(TKey key, TValue value)
	{
		Key = key;
		Value = value;
	}
	
	public KeyValuePair<TKey, TValue> AsKeyValuePair()
	{
		return new KeyValuePair<TKey, TValue> (Key, Value);
	}
}

public abstract class SerializedDictionary<TKeyValuePair, TKey, TValue> :
	IDictionary<TKey, TValue>
		where TKeyValuePair : SerializedKeyValuePair<TKey, TValue>
		where TKey : IEquatable<TKey>
{
	protected abstract List<TKeyValuePair> KeyValuePairs { get; }
	
	public abstract void Add (TKey key, TValue value);

	public bool ContainsKey (TKey key)
	{
		foreach (TKeyValuePair kvp in KeyValuePairs)
		{
			if (kvp.Key.Equals(key))
			{
				return true;
			}
		}

		return false;
	}

	public bool ContainsValue(TValue value)
	{
		EqualityComparer<TValue> equalityComparer = EqualityComparer<TValue>.Default;

		foreach (TKeyValuePair kvp in KeyValuePairs)
		{
			if (equalityComparer.Equals(kvp.Value, value))
			{
				return true;
			}
		}
		
		return false;
	}

	public bool Remove (TKey key)
	{
		for (int i = 0; i < KeyValuePairs.Count; i++)
		{
			TKeyValuePair kvp = KeyValuePairs*;*
  •  	if (kvp.Key.Equals(key))*
    
  •  	{*
    
  •  		KeyValuePairs.RemoveAt(i);*
    
  •  		return true;*
    
  •  	}*
    
  •  }*
    
  •  return false;*
    
  • }*

  • public bool TryGetValue (TKey key, out TValue value)*

  • {*

  •  foreach (TKeyValuePair kvp in KeyValuePairs)*
    
  •  {*
    
  •  	if (kvp.Key.Equals(key))*
    
  •  	{*
    
  •  		value = kvp.Value;*
    
  •  		return true;*
    
  •  	}*
    
  •  }*
    
  •  value = default(TValue);*
    
  •  return false;*
    
  • }*

  • public TValue GetValue(TKey key)*

  • {*

  •  foreach (TKeyValuePair kvp in KeyValuePairs)*
    
  •  {*
    
  •  	if (kvp.Key.Equals(key))*
    
  •  	{*
    
  •  		return kvp.Value;*
    
  •  	}*
    
  •  }*
    
  •  throw new ArgumentException("No value was found for the given key");*
    
  • }*

  • public void Add (KeyValuePair<TKey, TValue> item)*

  • {*

  •  Add (item.Key, item.Value);*
    
  • }*

  • public void Clear ()*

  • {*

  •  KeyValuePairs.Clear ();*
    
  • }*

  • public bool Contains (KeyValuePair<TKey, TValue> item)*

  • {*

  •  foreach (TKeyValuePair kvp in KeyValuePairs)*
    
  •  {*
    
  •  	if (kvp.Key.Equals(item.Key))*
    
  •  	{*
    
  •  		return EqualityComparer<TValue>.Default.Equals(kvp.Value, item.Value);*
    
  •  	}*
    
  •  }*
    
  •  return false;*
    
  • }*

  • public void CopyTo (KeyValuePair<TKey, TValue> array, int arrayIndex)*

  • {*

  •  for (int i = 0; i < KeyValuePairs.Count; i++)*
    
  •  {*
    

_ TKeyValuePair kvp = KeyValuePairs*;_
_
array[i + arrayIndex] = new KeyValuePair<TKey, TValue>(kvp.Key, kvp.Value);_
_
}_
_
}*_

* public bool Remove (KeyValuePair<TKey, TValue> item)*
* {*
* if (Contains(item))*
* {*
* Remove(item.Key);*
* return true;*
* }*

* return false;*
* }*

* public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator ()*
* {*
* foreach(TKeyValuePair kvp in KeyValuePairs)*
* {*
* yield return new KeyValuePair<TKey, TValue>(kvp.Key, kvp.Value);*
* }*

* yield break;*
* }*

* IEnumerator IEnumerable.GetEnumerator ()*
* {*
* return GetEnumerator ();*
* }*

* public TValue this[TKey key]*
* {*
* get { return GetValue(key); }*
* set*
* {*
* for (int i = 0; i < KeyValuePairs.Count; i++)*
* {*
_ if (KeyValuePairs*.Key.Equals(key))
{
KeyValuePairs.Value = value;
return;
}
}*_

* Add(key, value);*
* }*
* }*

* public ICollection Keys {*
* get*
* {*
* List list = new List();*

* foreach (TKeyValuePair kvp in KeyValuePairs)*
* {*
* list.Add(kvp.Key);*
* }*

* return list;*
* }*
* }*

* public ICollection Values {*
* get*
* {*
* List list = new List();*

* foreach (TKeyValuePair kvp in KeyValuePairs)*
* {*
* list.Add(kvp.Value);*
* }*

* return list;*
* }*
* }*

* public int Count { get { return KeyValuePairs.Count; } }*

* public bool IsReadOnly { get { return false; } }*

* public Dictionary<TKey, TValue> ToDictionary()*
* {*
* Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue> ();*

* foreach (TKeyValuePair kvp in KeyValuePairs)*
* {*
* dictionary.Add(kvp.Key, kvp.Value);*
* }*

* return dictionary;*
* }*
}
Then, I had to create sub-classes for each, based on the information being serialized. In my case, the Key type was a struct, so leaving the serialization up to the sub-class was handy since Unity doesn’t serialize structs:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using HexGame;

[System.Serializable]
public class MySerializedDictionary :
SerializedDictionary<MySerializedKeyValuePair, MyKey, MyValue>
{
* [SerializeField] private List m_keyValuePairs = new List();*

* protected override List KeyValuePairs*
* {*
* get { return m_keyValuePairs; }
_ }*_

* public override void Add (MyKey key, MyValue value)*
* {*
* if (ContainsKey(key))*
* {*
* throw new ArgumentException(“The dictionary already contains an entry with the specified key”);*
* }*
* else*
* {*
* KeyValuePairs.Add(new MySerializedKeyValuePair(key, value));*
* }*
* }*
}

[Serializable]
public class MySerializedKeyValuePair : SerializedKeyValuePair<MyKey, MyValue>
{
* [SerializeField] private int m_keyVariableA;
[SerializeField] private int m_keyVariableB;*

* [SerializeField] private MyValue m_value;*

* public override MyKey Key*
* {*
* get { return new MyKey(m_keyVariableA, m_keyVariableB); }
_ set*
* {_
m_keyVariableA = value.A;
m_keyVariableB = value.B;
_ }
}*_

* public override MyValue Value*
* {*
* get { return m_value; }
set { m_value = value; }
_ }*_

* public MySerializedKeyValuePair(MyKey key, MyValue value)*
* : base (key, value)*
* {}*
}

Here is my mini package of serializable dictionary in unity, with Monobehaviour and Scriptable Object variations

public class SerializableDictionary<TKey, TValue> : MonoBehaviour, ISerializationCallbackReceiver
{
    [SerializeField] private List<KeyValueEntry> entries;
    private List<TKey> keys = new List<TKey>();

    public Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

    [Serializable]
    class KeyValueEntry
    {
        public TKey key;
        public TValue value;
    }

    public void OnAfterDeserialize()
    {
        dictionary.Clear();

        for (int i = 0; i < entries.Count; i++)
        {
            dictionary.Add(entries_.key, entries*.value);*_

}
}

public void OnBeforeSerialize()
{
if (entries == null)
{
return;
}

keys.Clear();

for (int i = 0; i < entries.Count; i++)
{
keys.Add(entries*.key);*
}

var result = keys.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(x => new { Element = x.Key, Count = x.Count() })
.ToList();

if (result.Count > 0)
{
var duplicates = string.Join(", ", result);
Debug.LogError($“Warning {GetType().FullName} keys has duplicates {duplicates}”);
}
}
}
[207086-serializabledictionary.txt|207086]

I’m not sure did I solve your problem but I did something like this.

  1. First I created simple class:

    [Serializable] public class Dictionary { public Sprite Sprite; public ItemType ItemType; }

  2. Next I created second class extend by SerializableObject

[CreateAssetMenu(menuName = "Items/RiseableItem")] public class RiseabeItem : ScriptableObject { public List<Dictionary> Dictionaries; }

Then I can do a list of items with specific Key and Values in SerializableObject. This object can be use by many objects on a scane.

2019 My hack for this:

 [Serializable]
    public class VideoStruture
    {
        [SerializeField]
        public List<VideoClip> VideoCollection = new List<VideoClip>();
    
        [SerializeField]
         public string NameUnique = "";
    }

[Serializable, ExecuteInEditMode] public class LookArround : MonoBehaviour { [SerializeField]
    List<VideoStruture> Videos = new List<VideoStruture>();

    [ReadOnly]
    public int ForUniqueNameGeneration = 0;
    [ReadOnly]
    public int LastValidIndexForUniqueNames = 0;

 void OnValidate()
    {
        List<string> NamesToTestForUnique = new List<string>();
        List<VideoStruture> VideosTempList = new List<VideoStruture>();
        int ValidLastIndex = (LastValidIndexForUniqueNames < Videos.Count) ? LastValidIndexForUniqueNames : Videos.Count - 1; 

        if (Videos.Count > 0)
        {
            
            foreach (VideoStruture VideoSTR in Videos)
            {
                //Debug.Log("" + ForUniqueNameGeneration + " " + VideoSTR.NameUnique);
                //Debug.Log("I: "+Videos.IndexOf(VideoSTR) + " " + (NamesToTestForUnique.Contains(VideoSTR.NameUnique)
== false) + " " + (VideoSTR.NameUnique == "") + " " + (VideoSTR == null));
                if (VideoSTR.NameUnique=="" || VideoSTR.NameUnique == Videos[ValidLastIndex].NameUnique)
                {
                    VideoSTR.NameUnique = ForUniqueNameGeneration.ToString();
                    ForUniqueNameGeneration+=1;
                    
                    if(ForUniqueNameGeneration > 100000)
                    {
                        ForUniqueNameGeneration = 0;
                    }
                }

                if (NamesToTestForUnique.Contains(VideoSTR.NameUnique)==false )
                {
                    
                    VideosTempList.Add(VideoSTR);
                    NamesToTestForUnique.Add(VideoSTR.NameUnique);
                }

            }

            Videos = VideosTempList;
            LastValidIndexForUniqueNames = Videos.Count - 1;
            
        }

        else
        {
            LastValidIndexForUniqueNames = 0;
        }
    } }

public class ReadOnlyAttribute : PropertyAttribute {

}

[CustomPropertyDrawer(typeof(ReadOnlyAttribute))] public class ReadOnlyDrawer : PropertyDrawer {
    public override float GetPropertyHeight(SerializedProperty property,
                                            GUIContent label)
    {
        return EditorGUI.GetPropertyHeight(property, label, true);
    }

    public override void OnGUI(Rect position,
                               SerializedProperty property,
                               GUIContent label)
    {
        GUI.enabled = false;
        EditorGUI.PropertyField(position, property, label, true);
        GUI.enabled = true;
    } }

Hope it helps someone!

I get:

SerializationException: there are 1 keys and 0 values after deserialization. Make sure that both key and value types are serializable.
SerializableDictionary`2[TKey,TValue].OnAfterDeserialize () (at Assets/ScriptableObjects/ColourData.cs:31)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&) (at /home/bokken/build/output/unity/unity/Modules/IMGUI/GUIUtility.cs:189)

when I try to add key-value pair in the editor. Here is my ColorData scriptable object:

using UnityEngine;
using System.Collections.Generic;
using System;
using System.Runtime.Serialization;

[System.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 (var pair in this)
        {
            keys.Add(pair.Key);
            values.Add(pair.Value);
        }
    }

    // load dictionary from lists
    public void OnAfterDeserialize()
    {
        Clear();

        if (keys.Count != values.Count)
        {
            throw new SerializationException(
                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 (var i = 0; i < keys.Count; i++)
        {
            Add(keys_, values*);*_

}
}
}

[Serializable] public class Colours : SerializableDictionary<string, Color32> { }

[CreateAssetMenu]
public class ColourData : ScriptableObject
{
[SerializeField]
public Colours colours;
}

Hi, I have created a simple editor tool for serialising dictionary. Check this repo.