From two lists into a dictionary... and back?

I’ve been trying to wrap my head around Serialization and I’m still quite confused by data disappearing while I switch from EDIT to GAME mode.

I’m trying to get the component below to have a dictionary at its core. I’ve found multiple suggestions regarding using two Lists<> to load keys and values separately, to then stitch them together into a dictionary in the Awake() function. Sounds logical enough.

The problem is, if I modify the dictionary in EDIT mode, and short of keeping the two original lists synced with the dictionary at all times, how/when do I save the data of the dictionary back into the lists just before switching to GAME mode, so that Awake() can get up to date lists?

This is what I have so far:

using UnityEngine;
using System.Collections.Generic;

public class DictionaryLookAlike: MonoBehaviour
{
	public List<string> keys = new List<string>();
	public List<int> values = new List<int>();
	
	// Unity doesn't serialize dictionaries even if you pray in Klingon.
	private Dictionary<string, int> dictionary = new Dictionary<string, int>();
	
	public void Awake()
	{
		loadData();
	}

    // how/when do I put the data back into the lists prior to switching to GAME mode?
	public void OnWhat??????()
	{
		saveData();
	}
	
	// Dictionaries are not serializable in Unity so far.
	// For this reason we serialize two lists and stitch
	// those together during Awake().
	private void loadData()
	{ 		
		string aKey;
		int aValue;
		
		for(int index = 0; index < keys.Count; index++)
		{
			aKey = keys[index];
			aValue = values[index];
			
			dictionary[aKey] = aValue;
		}
		
		// we don't need the lists anymore, hopefully they will
		// be GC'd and free some memory until we need them again.
		keys.Clear();
		values.Clear();
	}
	
	// As dictionaries are not serialized we must save 
	// all the dictionary data back into the lists.
	private void saveData()
	{	
		foreach(var entry in dictionary)
		{
			keys.Add(entry.Key);
			values.Add(entry.Value);
		}	
	}
}

I’m not sure I understand where your problem lies. What’s the function of your saveData method? As you have it, your two Lists will feed a dictionary in Awake, which is what you want, no?

You could prevent errors in list lengths,as well as facilitate data input in the inspector with a serializable nested class :

[System.Serializable]
public class StringIntPair
{
     public int fooInt;
     public string fooString;
}

Then declare a public array of StringIntPair, it will show up in the inspector :

public StringIntPair[] keysAndValues;

This would prevent potential lengths mismatches. Then build your dictionnary as you’re already doing, in Awake.

To answer your question where you need to perform the serialization, the answer is in the callback of EditorApplication.playmodeStateChanged.

As to why I inherit from ScriptableObject, I could get a System.Object to serialize properly. I also don’t think a dictionary should be a component in a GameObject, because it’s a data structure. Instead, a Component should use it; hence a ScriptableObject.

EDIT:At some point in my coding the goal shifted from making a bare bones serializing dictionary to a serializing dictionary I can use in my projects, which is why all the unrelevant stuff in the previous code.

I will probably send this to the Asset Store when I’m finished; I’ve seen at least a few questions asking for a serializable dictionary.

EDIT: No, I went over the code too heavy handedly; it’s not every day I get to remove functionality from my code. This code does work for me; tested.

It’s also easy to break this code by changing values inside the dictionary; foreach doesn’t like its lists changing on it.

The Dictionary:

[System.Serializable] //<- important
public abstract class SerializableDictionary<K,V> : ScriptableObject{
	
	/* Lists to serialize the dictionary to.
	 * Notice that as they are generic fields, they will not serialize as-is.
	 * Instead, this class must be inherited by a non-generic class so Unity 
	 * knows what types they are and serializes correctly. That concrete class 
	 * is then used in applications
	 * 
	 * I.E. public class StringIntDictionary : SerializableDictionary<string,int> {}
	 */
	[SerializeField] //<- important
	public List<V> v;
	[SerializeField] //<- important
	public List<K> k;
	
	public Dictionary<K,V> dictionary;
	
	public SerializableDictionary(){
		dictionary = new Dictionary<K, V>();
		//Lists are null if it's the first time, otherwise they've been deserialized.
		if (v == null){
			v = new List<V>();
			k = new List<K>();
		}
	}
	
	//Does the deserializing.
	void OnEnable(){
		dictionary = new Dictionary<K, V>();
		for(int i=0;i<k.Count;i++){
			dictionary.Add( k_,v*);*_

* }*
* k.Clear();*
* v.Clear();*
* }*

* //Serializes dictionary*
* public void SerializeDictionary(){*
* v.Clear();*
* k.Clear();*
* foreach(KeyValuePair<K,V> kvp in dictionary)*
* {*
* k.Add(kvp.Key);*
* v.Add(kvp.Value);*
* }*
* }*
}
The Editor Script
=================
/We need this editor script to call the Serialize function of the serializing dictionary/
[CustomEditor(typeof(DictionaryTest))]
public class DictionaryEditor : Editor {

* [SerializeField]*
* DictionaryTest editorTarget;*

* private bool unfolded = true;*
* private string newKey = “”;*

_ //
/
The important stuff /
/
/_

* void Awake(){*
* editorTarget = (DictionaryTest)target;*
* //Register for the scene change callback.*
* EditorApplication.playmodeStateChanged += PlayChanged;*
* }*

* void OnEnable(){*
* if(editorTarget == null) {*
* editorTarget = (DictionaryTest)target;*
* }*

* if(editorTarget.dictionary == null){*
* editorTarget.dictionary = ScriptableObject.CreateInstance();*
* }*
* }*

* //This is called whenever the playmode changes*
* void PlayChanged(){*
* editorTarget.dictionary.SerializeDictionary();*
* }*

* void OnDestroy(){*
* //Unregister the scene change callback*
* EditorApplication.playmodeStateChanged -= PlayChanged;*
* }*
_ //
/
End of important stuff /
/
/_

* public override void OnInspectorGUI ()*
* { *
* DrawDefaultInspector();*
* EditorGUILayout.LabelField(“”);*
* unfolded = EditorGUILayout.Foldout(unfolded,“Dictionary”);*
* if(unfolded){*
* foreach(KeyValuePair<string,int> kvp in editorTarget.dictionary.dictionary){*
* GUILayout.BeginHorizontal();*
* ShowDictionaryEntry(kvp.Key, kvp.Value);*
* if(GUILayout.Button(“Remove Entry”))*
* editorTarget.dictionary.dictionary.Remove(kvp.Key);*
* GUILayout.EndHorizontal();*
* }*

* ShowAddEntryButton();*
* }*
* }*
* ///

*
* /// Shows the add button.*
* ///
*
* void ShowAddEntryButton(){*
* GUILayout.BeginHorizontal();*
* EditorGUILayout.PrefixLabel("Key: ");*
* newKey = GUILayout.TextField(newKey);*
* if(GUILayout.Button(“Add Entry”)){*
* editorTarget.dictionary.dictionary.Add(newKey,0);*
* newKey = string.Empty;*
* }*
* GUILayout.EndHorizontal(); *

* }*

* ///

*
* /// Shows the dictionary values.*
* ///
*
* void ShowDictionaryEntry(string k, int v){*
* int oldValue = v;*
* EditorGUILayout.PrefixLabel(k);*
* v = EditorGUILayout.IntField(v);*
* if(oldValue != v)*
* editorTarget.dictionary.dictionary[k] = v;*

* GUILayout.Label(" ");*
* }*
}
The test class
==============
[System.Serializable]
public class DictionaryTest : MonoBehaviour {

* [SerializeField]*
* [HideInInspector]*
* public StringIntDictionary dictionary;*

* void OnEnable(){*
* if( dictionary == null){*
* dictionary = ScriptableObject.CreateInstance();*
* } *
* }*

* void Start(){*
* dictionary.dictionary.Add(“test”,5);*
* }*

* void OnGUI(){*
* foreach(KeyValuePair<string,int> kvp in dictionary.dictionary){*
* GUILayout.Label(kvp.Key+" "+kvp.Value.ToString());*
* }*
* }*
}

Your code is logically correct, but serialized data is only updated in the Editor. It will not be saved when you stop running your game.

If you wish to save data you may want to write and read it from a file, which is a more complex procedure. This is one approach.

The downside of this is that you’ll have to do some further work if you still wish to modify the data in the Editor. You could write a custom editor that will read in the file and saves it out again.

A simpler and more idiomatic solution is to save the prefab. You’ll have to give the script a reference to a prefab object in your assets folder and it can copy itself into it. Use PrefabUtility.ReplacePrefab.

In game, you will have to choose when is most appropriate to save the data out for your program. You’ll at least want it in OnDestroy. OnDestroy will be called when your application quits.

Also, I’d recommend incorporating gregzo’s approach for the reasons he provides.

Good luck and feel free to comment if you’d like further advice.

##EDIT:

Sorry, I skimmed your question and just read your example code, guessing at what you were trying to do. Clearly I was wrong. (I’ll leave the above here anyway, even though it addresses a different question).

The problem is, if I modify the dictionary in the editor, and short of keeping the two original lists up to the date every time I modify the dictionary, how/when do I save the data of the dictionary back into the lists so that as I switch to GAME mode, Awake() gets invoked and it has up to date lists to correctly refill the dictionary?

Never modify the dictionary. The dictionary should exist only at runtime. If you’re working in the editor the performance of your data structure should not be important. Just do a linear search of the list.

I think you might be looking for OnEnable and OnDisable

OnDisable gets called before a recompile, and OnEnable afterwards. You’ll prolly want to add an if(Application.isPlaying) in there too, so you don’t have odd things happening in game. You’ll also need the [ExecuteInEditMode] tag

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class Test : MonoBehaviour {
	
	void Awake() {
		Debug.Log("Awake");
	} 
	void Start () {
		Debug.Log("Start");
	}
	void OnEnable() {
		Debug.Log("OnEnable");
	}
	void OnDisable() {
		Debug.Log("OnDisable");
	}
	
	void OnDestroy() {
		Debug.Log("OnDestroy");
	}
}

Output:

(On Creating the object/loading the editor)
Awake
OnEnable
Start

(On recompile)
OnDisable
OnEnable

(On Enter Play Mode)
OnDestroy
Awake
OnEnable
Start

Edit:
More specifically, it looks like you problem is not with going into Play mode, but with the serialization/deserialization of your class. There was a really nice video about this at Unite Nordic 2013 which I recommend watching.

Hi everyone,
I found a nice solution to make unity serialize dictionaries, using the ISerializationCallbackReceiver interface, see serialization in unity for more information. You can also find an example of the serialized dictionary.