- Home /
Dictionary serialization woes
I'm trying to serialise a large Dictionary via the network by turning it into a byte[] array, but I'm falling at the first hurdle or being able to serialise a dictionary to a byte array and back. You could repro my problem making an empty project and attaching the script below to any GameObject.
The deserialise in RtsPlayer.Awake fails with this error: SerializationException: The constructor to deserialize an object of type Rts.Dict was not found.
I've tried the solutions given in every article/question on the subject I could find, some of which are linked in the code, but I hit the same problem every time. Can anyone offer any help?
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Assertions;
using Rts;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
namespace Rts
{
class BinarySerialisation
{
// Serialisation functions: http://answers.unity3d.com/questions/800344/deserialization-issue-with-dictionary.html
public static byte[] ObjectToByteArray<T>(T obj)
{
MemoryStream m = new MemoryStream();
if (obj != null)
{
BinaryFormatter b = new BinaryFormatter();
b.Serialize(m, obj);
}
return m.ToArray();
}
public static T ByteArrayToObject<T>(byte[] arrBytes)
{
if (arrBytes == null || arrBytes.Length < 1)
return default(T);
BinaryFormatter binForm = new BinaryFormatter();
T obj = (T)binForm.Deserialize(new MemoryStream(arrBytes));
return obj;
}
}
// SerializableDictionary: http://answers.unity3d.com/questions/460727/how-to-serialize-dictionary-with-unity-serializati.html
[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>();
//public SerializableDictionary() {}
//public SerializableDictionary(SerializationInfo info, StreamingContext context) : base(info, context) {}
// 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]);
}
}
[Serializable]
class Dict : SerializableDictionary<int, string>{}
public class RtsPlayer : MonoBehaviour
{
void Awake()
{
Dict simpleDictionary = new Dict();
simpleDictionary[7] = "foo";
string foo = simpleDictionary[7];
byte[] simpleDictionaryBytes = BinarySerialisation.ObjectToByteArray(simpleDictionary);
Dict pendingActionsOut = BinarySerialisation.ByteArrayToObject<Dict>(simpleDictionaryBytes); // Fails with the following callstack:
/*
SerializationException: The constructor to deserialize an object of type Rts.Dict was not found.
System.Runtime.Serialization.ObjectRecord.LoadData (System.Runtime.Serialization.ObjectManager manager, ISurrogateSelector selector, StreamingContext context) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization/ObjectManager.cs:577)
System.Runtime.Serialization.ObjectManager.DoFixups () (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization/ObjectManager.cs:84)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadNextObject (System.IO.BinaryReader reader) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:145)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectGraph (BinaryElement elem, System.IO.BinaryReader reader, Boolean readHeaders, System.Object& result, System.Runtime.Remoting.Messaging.Header[]& headers) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:110)
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.NoCheckDeserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:179)
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:136)
Rts.BinarySerialisation.ByteArrayToObject[Dict] (System.Byte[] arrBytes) (at Assets/Scripts/RtsPlayer.cs:35)
Rts.RtsPlayer.Awake () (at Assets/Scripts/RtsPlayer.cs:88)
*/
Debug.LogError("If we got here it worked");
}
}
}
$$anonymous$$y Generic Serialiser class works with dictionaries.
http://www.doofah.com/tutorials/unity/generic-serialiser-class/
I've tried using this class, but I get exactly the same error on this line of LoadFromPlayerPrefs: T deserializedObject = (T)bFormatter.Deserialize(stream);
Here is my code using your class: http://hastebin.com/ewohuqokal.vala
You're trying to serialize an instance of your serializable dictionary class. You don't need to make your own version of a dictionary that implements it's own serialization logic.
Just pass an instance of a standard dictionary to my serializer class and it will work.
Here is an example, it creates a new dictionary, saves it to a binary file, then loads it into a new instance and log prints the value of the 1st element.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
public class SerializeDictionaryTest : $$anonymous$$onoBehaviour {
// Use this for initialization
void Start () {
Dictionary<int, string> myDict = new Dictionary<int, string>();
myDict.Add(1,"1st entry");
myDict .Add(2,"2nd entry");
Doofah.GenericSerialiser.SaveToBinaryFile(Path.Combine(Application.persistentDataPath, "$$anonymous$$yDictionary.bin"), myDict);
var loadedDict = Doofah.GenericSerialiser.LoadFromBinaryFile<Dictionary<int,string>>(Path.Combine(Application.persistentDataPath, "$$anonymous$$yDictionary.bin"));
Debug.Log(string.Format("Value at element zero = {0}", loadedDict[1]));
}
}
Why do you have commented out the parameter less constructor and the serialization constructor? Do they throw any errors? It's possible that you need to implement those in the concrete class ("Dict") as well.
$$anonymous$$eep in $$anonymous$$d that you don't need that "SerializableDictionary" class unless you want to have Unity serialize that dictionary inside the editor so you can load it at runtime.
If you just want to use a Dictionary at runtime to send data over the network, just use an ordinary Dictionary which should be serializable by default (serializable by the BinaryFormatter, not by Unity).
I'll have a go rewriting the using ISerializable. I commented the ctor out as it didn't fix the problem, and I was trying to get rid of everything that wasn't required.
Have you read my suggestion to actually implement those constructor in the Dict class? Since in the end that's the one that need to be created by the deserializer and the error complains that it can't find a proper constructor.
Constructors are not inherited with the exception of the parameterless constructor (if available). Any specialized constructors are not propergated to derived classes. So you need to implement those explicitly in those classes.
But again, i see no reason why using the Dict / SerializableDictionary class in the first place. They just add unnecessary overhead when you want to send it over the network.
This version still hits the same issue: http://hastebin.com/puqukokado.vala
Answer by Bunny83 · May 12, 2016 at 10:50 PM
As i said in the comment, this fixes the issue:
[Serializable]
class Dict : SerializableDictionary<int, string>
{
public Dict():base() { }
public Dict(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
I just tested it in Unity.
The version you posted on hastebin doesn't implement any constructor in the Dict class. Again constructors are not inherited.
Sorry, I see your point. I had tried every part of what was needed, but not all together :$
$$anonymous$$any thanks for the help!
Answer by Marquo · May 12, 2016 at 08:25 PM
In case anyone else finds this same issue, I believe my problem is that ISerializationCallbackReceiver is an editor only interface, so these functions don't work at runtime.
In the editor Munchy2007's class will work great
ISerializationCallbackReceiver is not an editor only interface. It's true that "OnBeforeSerialize" is only called inside the editor since that's the only place where the Unity serializer actually serializes any data. But "OnAfterDeserialize" has to work at runtime in order to actually read the serialized assets that has been packed into the game.
You don't seem to have problems with Unity's serialization system but with the usual .NET BinarySerializer
So how would I go about making a class serialise/deserialise using the BinaryFormatter/$$anonymous$$emoryStream shown in the code?
Your answer
Follow this Question
Related Questions
Help with editor serialization 1 Answer
In this case the lists do not serialize. Why? 2 Answers
JsonUtility not found / working 0 Answers