(SOLUTION) How to serialize interfaces, generics, auto-properties and abstract System.Object classes?

So as a programmer I care a lot about my code quality and good coding practices. I like to write clean, easy to understand, well-abstracted, non-cumbersome code. For that, I use many techniques in my arsenal such as interfaces, properties, polymorphism, etc.

Unfortunately Unity doesn’t seem to like that. There was always issues with polymorphic serialization, Unity never encouraged the use of interfaces nor properties cause they don’t play well with its ‘serialization system’. Are you saying that due to Unity’s technicalities we can’t practice good programming habits and write cool designs?!

Unity is a great engine with a very fluid work-flow, but they always seem to neglect their framework and API from the aspect I mentioned above (supporting interfaces, properties etc) - That has always been the concern of a lot of us for very long times, even since Unity 3.x

With Unity 5 approaching, it’s a pity that there’s not yet a solution to this problem!

Note to Unity: Please order your priorities well, I mean

“Want a new Mono? Want a better programming framework? Alright, here’s an Audio-mixer! :)”

???

EDIT: I’ve released my VFW (free) with tons of features, pretty much a ShowEmAll on steroids


As it is common when working around Unity’s limitations, the solution is mostly a hack taking advantage of the new ISerializationCallback interface that’s been added recently.

We can write our own serialization system to serialize only the things that Unity doesn’t. Mainly: interfaces, generic types, (1)auto-properties and (2)abstract System.Object classes

  1. I said auto-properties because it doesn’t make much sense to serialize a property with a backing field. If you wanted to do that, just serialize the backing field instead.
  2. Unity does serialize abstract classes, but only if they inherit UnityEngine.Objects (like MonoBehaviours)

The core logic is (I will explain interfaces - serializing auto-props, generics etc is very similar):

  1. Given a MonoBehaviour we get all the fields/properties that Unity can’t serialize (in our example, interfaces)
  2. To make the interface field/property survive an assembly reload, we check the actual implementer of that interface, if it’s a UnityEngine.Object (mostly a MonoBehaviour) we cast it down to a UnityEngine.Object and store the reference in a serialized dictionary or a list.
  3. Otherwise if the implementer is not a UnityEngine.Object, we have to serialize it manually. (We’ll use BinaryFormatter for the sake of simplicity)
  4. Great, but what if the interface contains a UnityEngine.Object reference, like a Transform or something? How will the serializer know how to serialize UnityObjects? - The answer is that it doesn’t and can’t know. So we write ‘surrogates’ for those Unity objects (Vector3, Vector2, Transform, Rect, etc) - This might sound like a very tedious job, but it’s not, bare with me.

So let’s start writing our new “SerializedBehaviour” which will act as a standard to inherit from instead of MonoBehaviour:

public abstract class SerializedBehaviour : MonoBehaviour, ISerializationCallbackReceiver
{
	public void OnAfterDeserialize()
	{
		Deserialize();
	}

	public void OnBeforeSerialize()
	{
		Serialize();
	}

	private void Serialize()
	{
	}

	private void Deserialize()
	{
	}
}

Let’s implement those suckers!

public abstract class SerializedBehaviour : MonoBehaviour, ISerializationCallbackReceiver
{
	private Dictionary<string, UnityObject> serializedObjects = new Dictionary<string, UnityObject>();
	private Dictionary<string, string> serializedStrings = new Dictionary<string, string>();
	private BinaryFormatter serializer = new BinaryFormatter();

	public void OnAfterDeserialize()
	{
		Deserialize();
	}

	public void OnBeforeSerialize()
	{
		Serialize();
	}

	private void Serialize()
	{
		foreach (var field in GetInterfaces())
		{
			var value = field.GetValue(this);
			if (value == null)
				continue;

			string name = field.Name;
			var obj = value as UnityObject;
			if (obj != null) // the implementor is a UnityEngine.Object
			{
				serializedObjects[name] = obj; // using the field's name as a key because you can't have two fields with the same name
			}
			else
			{
				// try to serialize the interface to a string and store the result in our other dictionary
				using (var stream = new MemoryStream())
				{
					serializer.Serialize(stream, value);
					stream.Flush();
					serializedObjects.Remove(name); // it could happen that the field might end up in both the dictionaries, ex when you change the implementation of the interface to use a System.Object instead of a UnityObject
					serializedStrings[name] = Convert.ToBase64String(stream.ToArray());
				}
			}
		}
	}

	private void Deserialize()
	{
		foreach (var field in GetInterfaces())
		{
			object result = null;
			string name = field.Name;

			// Try and fetch the field's serialized value
			UnityObject obj;
			if (serializedObjects.TryGetValue(name, out obj)) // if the implementor is a UnityObject, then we just fetch the value from our dictionary as the result
			{
				result = obj;
			}
			else // otherwise, get it from our other dictionary
			{
				string serializedString;
				if (serializedStrings.TryGetValue(name, out serializedString))
				{
					// deserialize the string back to the original object
					byte[] bytes = Convert.FromBase64String(serializedString);
					using (var stream = new MemoryStream(bytes))
						result = serializer.Deserialize(stream);
				}
			}

			field.SetValue(this, result);
		}
	}

	private IEnumerable<FieldInfo> GetInterfaces()
	{
		return GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
						.Where(f => !f.IsDefined<HideInInspector>() && (f.IsPublic || f.IsDefined<SerializeField>()))
						.Where(f => f.FieldType.IsInterface);
	}
}

Now we could write:

public class SerializationTest : SerializedBehaviour
{
	public ITestInterface test;
}

public interface ITestInterface
{
	string StringValue { get; set; }
	float FloatValue { get; set; }
}

[Serializable] // don't forget to add this attribute if you're using BinaryFormatter
public class SystemImplementer : ITestInterface
{
	public string StringValue { get; set; }
	public float FloatValue { get; set; }
}

public class UnityImplementer : MonoBehaviour, ITestInterface
{
	public string StringValue { get; set; }
	public float FloatValue { get; set; }
}

Just to test things out in-editor and see if the values really persist/survive assembly reload:

[CustomEditor(typeof(SerializationTest))]
public class SerializationTestEditor : Editor
{
	public override void OnInspectorGUI()
	{
		base.OnInspectorGUI();

		var typedTarget = target as SerializationTest;
		if (GUILayout.Button("Set to system implementor"))
			typedTarget.test = new SystemImplementer();
		if (GUILayout.Button("Set to unity implementor"))
			typedTarget.test = UnityEngine.Object.FindObjectOfType<UnityImplementer>() ?? new GameObject().AddComponent<UnityImplementer>();
		if (GUILayout.Button("Print value"))
			Debug.Log(typedTarget.test);
	}
}

Now do the following:

  1. Create an empty game object with
    SerializationTest attached (you should see the custom editor buttons)
  2. Assign the test field to the system implementer or unity implementer (via the buttons)
  3. Enter play mode, and press the print value button

What did you expect? Of course it doesn’t work, because we’re using naked Dictionary<,> we need some serializable dictionaries (while writing and testing this, I tried using lists with keys/values but it’s not so great) - Grab this dictionary implementation from here to move on.

First let’s create the dictionaries we need:

[Serializable]
public class StrObjDict : KVPListsDictionary<string, UnityObject>
{
}

[Serializable]
public class StrStrDict : KVPListsDictionary<string, string>
{
}

Now replace the two Dictionary<,>ies in our SerializedBehaviour with:

[SerializeField] private StrObjDict serializedObjects = new StrObjDict(); 
[SerializeField] private StrStrDict serializedStrings = new StrStrDict(); 

Do the same experiment, enter play mode and if everything worked out well, the interface value should persist! Now that’s pretty metal!

But we have a problem, try and add a Vector3 or Transform field to our test interface, things will not serialize! The problem is that BinaryFormatter can’t serialize classes not marked up with Serializable - But what to do if we can’t touch Unity’s code? The solution is ‘surrogates’, add the following surrogate (anywhere)

public class Vector3Surrogate : ISerializationSurrogate
{
	public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
	{
		var vector = (Vector3)obj;
		info.AddValue("x", vector.x);
		info.AddValue("y", vector.y);
		info.AddValue("z", vector.z);
	}

	public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
	{
		Func<string, float> get = name => (float)info.GetValue(name, typeof(float));
		return new Vector3(get("x"), get("y"), get("z"));
	}
}

This should handle serializing Vector3s, now let’s modify our serializer code a bit to tell it to use this surrogate - change the declaration of our serializer to be:

	private BinaryFormatter mSerializer; // don't instantiate here

	private BinaryFormatter serializer
	{
		get
		{
			if (mSerializer == null)
			{
				mSerializer = new BinaryFormatter();
				var selector = new SurrogateSelector();

				Action<Type, ISerializationSurrogate> addSurrogate = (type, surrogate) =>
					selector.AddSurrogate(type, new StreamingContext(), surrogate);

				addSurrogate(typeof(Vector3), new Vector3Surrogate());
				// add more custom surrogates here

				serializer.SurrogateSelector = selector;
			}
			return mSerializer;
		}
	}

Now our serializer knows how to seiralize Vector3s. But wait, what about the rest of the Unity arsenal? Transform, GameObject, etc? Well, here’s where hacking comes in, we’ll write only ‘one’ surrogate for ‘all’ UnityEngine.Objects!

public class UnityObjectSurrogate : ISerializationSurrogate
{
	private StrObjDict serializedObjects;

	public UnityObjectSurrogate(StrObjDict serializedObjects)
	{
		this.serializedObjects = serializedObjects;
	}

	public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
	{
		string fieldName = context.Context as string;
		serializedObjects[fieldName] = obj as UnityObject;
	}

	public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
	{
		string fieldName = context.Context as string;
		return serializedObjects[fieldName];
	}
}

Back to our serializer code, change the getter to:

	private BinaryFormatter serializer
	{
		get
		{
			if (mSerializer == null)
			{
				mSerializer = new BinaryFormatter();
				var selector = new SurrogateSelector();

				Action<Type, ISerializationSurrogate> addSurrogate = (type, surrogate) =>
					selector.AddSurrogate(type, new StreamingContext(), surrogate);

				addSurrogate(typeof(Vector3), new Vector3Surrogate());
				// add more custom surrogates here

				// create our unity surrogate
				var unitySurrogate = new UnityObjectSurrogate(serializedObjects);

				// get all unity object types
				var unityTypes = typeof(UnityObject).Assembly.GetTypes()
												    .Where(t => typeof(UnityObject).IsAssignableFrom(t))
												    .ToArray();

				// add our surrogate to let the serializer use it to handle unity objects serialization
				foreach (var t in unityTypes)
					addSurrogate(t, unitySurrogate);

				serializer.SurrogateSelector = selector;
			}
			return mSerializer;
		}
	}

Now you can have UnityObject references inside your interfaces and things will work just fine!

Here’s the full code for the demo.

But please don’t use this implementation, this was quick and dirty. I tried to make it as simple and easy to understand as I can, it was just for demo purposes, It doesn’t cover serializing properties nor generics. For that, use the implementation in my VFW Linked here

Stay brutal! \m/

You give urself quest and answered. Give us a chance. Not fair. :slight_smile:
First what type is UnityObject? You mean UnityEngine.Object right? :stuck_out_tongue:
I believe that in VFW(understand this was a concept) you not use hardcoded reference “serializedObjects” in Binary Formatter Class. All efforts to have one BinFormater in all code failed and you generated surrogate that only works in SerializedBehaviour and that’s it.
:stuck_out_tongue:
I know you like Dictionaries and like they are more readable so you use “name” strings but serialization is bottle neck, you need speed.
For serializedObjects you can use Dict and avoid nasty Convert.ToBase64String(stream.ToArray()); with that you lost all good of having bytearray, instead huge bytearray into string (plus conversion time)
by use of Dict<ordNum,byte[]>. Shhh don’t tell anyone. Secret is the Unity will serialize “byte[]”.
So I came up to more general Surrogate :wink: from the constructor idea you have me as Constructor is called once and on a main thread :smiley:

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


namespace ws.winx.unity.surrogates{

	public class UnityObjectSurrogate : ISerializationSurrogate
	{
		UnityEngine.Object[] objectsUnity;

		public UnityObjectSurrogate(){
			Debug.Log ("UnityObjectSurrogate Constructor:"+System.Threading.Thread.CurrentThread.ManagedThreadId);
			objectsUnity = UnityEngine.Object.FindObjectsOfType<UnityEngine.Object>();
		}


		public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
		{
			var objUnity = (UnityEngine.Object)obj;

			Debug.Log ("GetObjectData:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
			//Debug.Log("GetObjectData:"+go.name+" ID:"+go.GetInstanceID());
			info.AddValue("ID", objUnity.GetInstanceID());



		}



		/// <summary>
		/// Sets the object data.
		/// 
		/// !!! Not working cos Main Thread restriction
		/// </summary>
		/// <returns>The object data.</returns>
		/// <param name="obj">Object.</param>
		/// <param name="info">Info.</param>
		/// <param name="context">Context.</param>
		/// <param name="selector">Selector.</param>
		public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
		{


			Debug.Log ("SetObjectData:" + System.Threading.Thread.CurrentThread.ManagedThreadId);

			int ID = (int)info.GetValue ("ID", typeof(int));


		//	Debug.Log ("SetObjectData " + System.Threading.Thread.CurrentThread.ManagedThreadId + "ID:" + ID);
			return objectsUnity.FirstOrDefault (itm => itm.GetInstanceID () == ID);
			//return null;




		}
	}
}

Found this on Google in 2019, so maybe this info might be helpful for others: Odin Inspector can serialize interfaces.

Hello Sir, @vexe

I have one query regarding List collection.
If I am having some kind of transform List like,
public List gridCells;

Then how could I serialize ‘gridCells’…?
I want sequence of this list in next game launch…

Thanks in advance. :slight_smile: