- Home /
[Solved]How to serialize Dictionary with Unity Serialization System
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[i], valuesList[i]);
}
}
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
There's an open Feedback issue for dictionary serialization, please consider voting for it: https://feedback.unity3d.com/suggestions/serialize-generic-dictionary
Answer by hiddenspring81 · May 23, 2013 at 05:29 PM
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.
Of course this still works. Unity has some built in types which are designed to be used that way. For example the generic UnityEvent types.
If it doesn't work for you, you either use types which are not supported or one of your base classes is not marked as Serializable. If you need help with your specific problem please ask a seperate question and do not bump old answered questions.
Adding comment over the fold, so others don't waste time like I did.
This answer does not apply to the OP, which is about Dictionaries. The solution outlined here, will NOT work for Dictionaries.
It does apply to the OP. Have you read the OP in its entirety? The OP clearly states that he's well aware of the fact that Unity can't serialize dictionaries and he needs an alternative. The actual issues he has is, i quote:
The problem is that none of my variables are serialized, even the simple List and List.
He already uses a replacement for the Dictionary class. However he just created another generic class. Unity (currently) can't handle generic classes. You have to create a concrete non-generic implementation of that $$anonymous$$ap class.
So next time I would recommend to first read the question in order to understand the answer ^^. The answer 42 doesn't help anyone without knowing the question -.-
This works as of 2019.3 I commented this because people kept saying it doesn't work when it clearly works. Remember to use the attribute System.Serializable.
Answer by christophfranke123 · Oct 14, 2014 at 07:59 PM
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[i], values[i]);
}
}
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.
This solution is perfect, I'm borrowing this. I did not know of ISerializationCallbackReceiver, it looks like this will save me a lot of future headaches.
I modified it slightly myself to avoid copying UnityEngine.Objects that have been destroyed in the event that they're used as a key:
if (typeof(T$$anonymous$$ey).IsSubclassOf(typeof(UnityEngine.Object)) || typeof(T$$anonymous$$ey) == typeof(UnityEngine.Object)) {
foreach (var element in this.Where(element => element.$$anonymous$$ey != null)) {
_keys.Add(element.$$anonymous$$ey);
_values.Add(element.Value);
}
} else {
foreach (var element in this) {
_keys.Add(element.$$anonymous$$ey);
_values.Add(element.Value);
}
}
Wonderful solution. This works with no extra tinkering. As someone new to C#, could I just add this into it's own C# file and call it from multiple scripts?
Very helpful. I am going to look at a way to create a custom inspector for this now :)
When trying to use this I get the error 'The constructor to deserialize an object of type DictionaryOfStringAndInt was not found'. I'm new to serialization and am not sure what this constructor would look like. Can anyone help?
@mfshaw:
Could it be that you use some sort of .NET serialization like a BinaryFormatter? This question is about serialization inside the Unity editor using Unity's serialization system.
If you get that error from Unity's serialization system, that means you defined a parameterized constructor but no parameterless constructor. Unity needs a parameterless constructor in order to create an instance of the class. If you don't define any constructors yourself, the class automatically gets a public parameterless constructor. However when you implement any other constructor the parameterless constructor isn't generated automatically.
If this doesn't answer your question, please ask a seperate question about your specific case and do not ask questions in comments on other questions / answers. This is just getting messy.
I edited this to rid myself of errors in the console and keep keys and values together.
You can see source code for the dictionary here.
Just like the original, you have to implement the generic SerializableDictionary, but you'll also have to implement the generic Serializable$$anonymous$$eyValuePair.
You can see code for an example implementation here.
Answer by vexe · Jan 04, 2015 at 11:00 AM
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.
I've used Vexe for in-game assets just fine.
Haven't tried it yet for serializing to/from a text file. Is that feasible or simple?
Answer by JoaquinRD · Mar 30, 2014 at 08:56 PM
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[i];
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[i];
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[i].Key.Equals(key))
{
KeyValuePairs[i].Value = value;
return;
}
}
Add(key, value);
}
}
public ICollection<TKey> Keys {
get
{
List<TKey> list = new List<TKey>();
foreach (TKeyValuePair kvp in KeyValuePairs)
{
list.Add(kvp.Key);
}
return list;
}
}
public ICollection<TValue> Values {
get
{
List<TValue> list = new List<TValue>();
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<MySerializedKeyValuePair> m_keyValuePairs = new List<MySerializedKeyValuePair>();
protected override List<MySerializedKeyValuePair> 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)
{}
}
Just something you should note is that what you have there is just a very inefficient list, not an actual dictionary (from an implementation viewpoint).
The advantage of a dictionary.hashtable is the O(1)
lookup time. Your implementation has an O(n)
lookup time, which defeats the whole use of dictionaries.
You might as well just use a List
from the beginning, for all it's worth ;)
You're right that this is just a glorified list without the performance benefit of an actual dictionary, but the problem that it solves is serialization. I am personally only using this as an editor helper class that allows me to store a "dictionary" in the scene file. At runtime, I build an actual dictionary with the ToDictionary method and use that ins$$anonymous$$d.
It's true that I could still just serialize a list and build a dictionary from that, but I prefer to always think of the collection as a dictionary, rather than switching back and forth.
Answer by Gerappa · Oct 30, 2018 at 07:30 PM
I'm not sure did I solve your problem but I did something like this.
First I created simple class:
[Serializable] public class Dictionary { public Sprite Sprite; public ItemType ItemType; }
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.