In previous part I explained a way to serialize object to JSON format using reflection this time we will use reflection to deserialize object using reflection. While much of deserialization can be achieved by simply using the SetValue method the syntax for creating arrays, lists and objects is a bit more complex.
Deserializing object
First we start by creating a static class with method to deserialize object of generic type. Type argument is required as we only included type-names to objects within a collection to support polymorphism. Returning objects instead of known type with methods like this tend to lead to messy patterns with lot’s of ifs or similar to SerializeField in previous article and DeserializeField later in this article.
We will also use Activator to Create a new instance of the given type that we will then fill with the data we have serialized.
using System.Reflection;
using System.Collections.Generic;
using System.Collections;
using SimpleJSON;
using System;
public static class ObjectDeserializer
{
public static T DeserializeObject<T>(string json)
{
JSONNode js = JSONObject.Parse(json);
Type objectType = typeof(T);
T newInstance = (T)Activator.CreateInstance(objectType);
DeserializeObject(newInstance, js);
ObjectSerializer.SerializeObject(newInstance);
return newInstance;
}
}
Just like with Serializing we will get all the fields from the type we are deserializing but in this case it will give us information on fields that have been serialized.
There is however one problem that’s left unresolved here which is the case where objects or their fields get renamed which could potentially lead to loss of data. There are number of ways to fix this but none of them very pretty.
One that is being used by unity is the FormerySerializedAsAttribute which one could check for each field to see if serialized data with the old field name exists. Another one would be to implement a solver of sorts that can detect these changes and fix the serialized data accordingly. Simplest is probably to use text editor like NotePad++ with JSON plugin to browse the serialized data and find and replace all the renamed fields.
static void DeserializeObject(object targetObject, JSONNode data, object rootObject = null)
{
if (rootObject == null)
rootObject = targetObject;
FieldInfo[] fieldInfos = targetObject.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < fieldInfos.Length; i++)
DeserializeField(fieldInfos[i], data, rootObject);
}
We can use field type and field name to fetch the matching data from the serialized object which we can then cast to it’s correct type and use the Field.SetValue method to finally set the value of the field.
With arrays we use the Array.CreateInstance method to create the array with the length information we previously serialized with SerializeField. Arrays also have their own Array.SetValue method which we will use before finally setting the value of the field with the array we’ve created.
With Lists we need type that contains the generic type information. Here we create the type using Type.MakeGenericType method. Then we use Activator to create a new list instance with the given type which we cast to IList which we can use to add new items to the list.
When deserializing objects we again use activator to create new instance based on the field type or the serialized type with objects within collection. Then deserialize the object with recursion calling the DeserializeObject again to get the deserialized version of the object to set to field in question.
static void DeserializeField(FieldInfo field, JSONNode data, object rootObject)
{
Type fieldType = field.FieldType;
if (fieldType.IsGenericType)
fieldType = fieldType.GetGenericTypeDefinition();
if (fieldType.IsArray)
fieldType = typeof(Array);
if (fieldType == typeof(int) || fieldType.IsEnum)
{
field.SetValue(rootObject, data[field.Name].AsInt);
}
else if (fieldType == typeof(float))
{
field.SetValue(rootObject, data[field.Name].AsFloat);
}
else if (fieldType == typeof(bool))
{
field.SetValue(rootObject, data[field.Name].AsBool);
}
else if (fieldType == typeof(string))
{
field.SetValue(rootObject, data[field.Name].Value);
}
else if (fieldType == typeof(Array))
{
Type arrayType = field.FieldType;
int rank = arrayType.GetArrayRank();
JSONArray jsArray = data[field.Name]["Array"].AsArray;
Type elementType = arrayType.GetElementType();
Array arr;
if (rank == 1)
{
int lenght = data[field.Name]["Lenght"].AsInt;
arr = Array.CreateInstance(elementType, lenght);
if (elementType.IsPrimitive || elementType == typeof(string))
{
if (elementType == typeof(int) || elementType.IsEnum)
{
for (int i = 0; i < arr.Length; i++)
arr.SetValue(jsArray[i].AsInt, i);
}
else if (elementType == typeof(float))
{
for (int i = 0; i < arr.Length; i++)
arr.SetValue(jsArray[i].AsFloat, i);
}
else if (elementType == typeof(bool))
{
for (int i = 0; i < arr.Length; i++)
arr.SetValue(jsArray[i].AsBool, i);
}
else if (elementType == typeof(string))
{
for (int i = 0; i < arr.Length; i++)
arr.SetValue(jsArray[i].Value, i);
}
}
else if (elementType.IsClass || (elementType.IsValueType && !elementType.IsEnum))
{
for (int i = 0; i < arr.Length; i++)
{
string typeString = jsArray[i]["Type"].Value;
Type entryType = Type.GetType(typeString);
object o = Activator.CreateInstance(entryType);
DeserializeObject(o, jsArray[i]["Value"], o);
arr.SetValue(o, i);
}
}
field.SetValue(rootObject, arr);
}
else if (rank == 2)
{
int lenght1 = data[field.Name]["Lenght1"].AsInt;
int lenght2 = data[field.Name]["Lenght2"].AsInt;
arr = Array.CreateInstance(elementType, lenght1, lenght2);
if (elementType.IsPrimitive || elementType == typeof(string))
{
if (elementType == typeof(int) || elementType.IsEnum)
{
for (int i = 0; i < arr.GetLength(0); i++)
for (int j = 0; j < arr.GetLength(1); j++)
arr.SetValue(jsArray[i].AsInt, i, j);
}
else if (elementType == typeof(float))
{
for (int i = 0; i < arr.GetLength(0); i++)
for (int j = 0; j < arr.GetLength(1); j++)
arr.SetValue(jsArray[i].AsFloat, i, j);
}
else if (elementType == typeof(bool))
{
for (int i = 0; i < arr.GetLength(0); i++)
for (int j = 0; j < arr.GetLength(1); j++)
arr.SetValue(jsArray[i].AsBool, i, j);
}
else if (elementType == typeof(string))
{
for (int i = 0; i < arr.GetLength(0); i++)
for (int j = 0; j < arr.GetLength(1); j++)
arr.SetValue(jsArray[i].Value, i, j);
}
}
else if (elementType.IsClass || (elementType.IsValueType && !elementType.IsEnum))
{
for (int i = 0; i < arr.GetLength(0); i++)
{
for (int j = 0; j < arr.GetLength(1); j++)
{
string typeString = jsArray[i][j]["Type"].Value;
Type entryType = Type.GetType(typeString);
object o = Activator.CreateInstance(entryType);
DeserializeObject(o, jsArray[i][j]["Value"], o);
arr.SetValue(o, i, j);
}
}
}
field.SetValue(rootObject, arr);
}
}
else if (fieldType == typeof(List<>))
{
JSONArray jsArray = data[field.Name].AsArray;
int entryCount = jsArray.Count;
Type genericArgument = field.FieldType.GetGenericArguments()[0];
Type constructedListType = (typeof(List<>).MakeGenericType(genericArgument));
IList instance = (IList)Activator.CreateInstance(constructedListType);
if (genericArgument.IsPrimitive || genericArgument == typeof(string))
{
if (genericArgument == typeof(int) || genericArgument.IsEnum)
{
for (int i = 0; i < entryCount; i++)
instance.Add(jsArray[i].AsInt);
}
else if (genericArgument == typeof(float))
{
for (int i = 0; i < entryCount; i++)
instance.Add(jsArray[i].AsFloat);
}
else if (genericArgument == typeof(bool))
{
for (int i = 0; i < entryCount; i++)
instance.Add(jsArray[i].AsBool);
}
else if (genericArgument == typeof(string))
{
for (int i = 0; i < entryCount; i++)
instance.Add(jsArray[i].Value);
}
}
else if (genericArgument.IsClass || (genericArgument.IsValueType && !genericArgument.IsEnum))
{
for (int i = 0; i < entryCount; i++)
{
string typeString = jsArray[i]["Type"].Value;
Type entryType = Type.GetType(typeString);
object o = Activator.CreateInstance(entryType);
DeserializeObject(o, jsArray[i]["Value"], o);
instance.Add(o);
}
}
field.SetValue(rootObject, instance);
}
else if (fieldType == typeof(object))
{
JSONNode objectNode = data[field.Name];
object newInstance = Activator.CreateInstance(fieldType);
DeserializeObject(newInstance, data[field.Name].AsObject, newInstance);
field.SetValue(rootObject, newInstance);
}
}