Skip to content

Instantly share code, notes, and snippets.

@Hello-Meow
Created January 23, 2020 02:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Hello-Meow/306520e10632109a54e64d1e23c7b355 to your computer and use it in GitHub Desktop.
Save Hello-Meow/306520e10632109a54e64d1e23c7b355 to your computer and use it in GitHub Desktop.
A serializer using Unity's JsonUtility that also serializes referenced ScriptableObjects
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
/// <summary>
/// Serializer for objects that include ScriptableObject references.
/// </summary>
public class ScriptableObjectSerializer
{
private List<ScriptableObject> scriptableObjects;
private HashSet<FieldInfo> traverse;
private List<SerializedObject> serializedObjects;
private ScriptableObjectSerializer()
{
serializedObjects = new List<SerializedObject>();
}
/// <summary>
/// Serialize an object to JSON.
/// </summary>
/// <param name="obj">The object to serialize.</param>
/// <returns>JSON representation of the object and referenced ScriptableObjects.</returns>
public static string ToJson(object obj)
{
ScriptableObjectSerializer serializer = new ScriptableObjectSerializer();
return serializer.Serialize(obj);
}
/// <summary>
/// Deserialize an object from JSON.
/// </summary>
/// <typeparam name="T">The type of object to deserialize.</typeparam>
/// <param name="json">The json representation of the object(s).</param>
/// <returns>An instance of the object.</returns>
public static T FromJson<T>(string json)
{
ScriptableObjectSerializer serializer = new ScriptableObjectSerializer();
return (T)serializer.Deserialize(json, typeof(T));
}
/// <summary>
/// Deserialize an object from JSON.
/// </summary>
/// <param name="json">The json representation of the object(s).</param>
/// <param name="type">The type of object to deserialize.</param>
/// <returns>An instance of the object.</returns>
public static object FromJson(string json, Type type)
{
ScriptableObjectSerializer serializer = new ScriptableObjectSerializer();
return serializer.Deserialize(json, type);
}
private string Serialize(object obj)
{
GetTraverse(obj.GetType());
GetScriptableObjects(obj);
foreach (ScriptableObject scriptableObject in scriptableObjects)
serializedObjects.Add(new SerializedObject(scriptableObject));
if (!(obj is ScriptableObject))
serializedObjects.Add(new SerializedObject(obj));
string[] json = new string[serializedObjects.Count];
for (int i = 0; i < json.Length; i++)
json[i] = serializedObjects[i].json;
return string.Join(Environment.NewLine, json);
}
private object Deserialize(string json, Type type)
{
GetObjects(json);
int numObjects = serializedObjects.Count;
int numScriptableObjects = numObjects;
SerializedObject lastObject = serializedObjects[numObjects - 1];
if (lastObject.instanceID == string.Empty)
numScriptableObjects--;
if (lastObject.type != type.AssemblyQualifiedName)
throw new ArgumentException(string.Format("JSON was serialized as the type '{0}'.", lastObject.type));
ScriptableObject[] scriptableObjects = new ScriptableObject[numScriptableObjects];
for (int i = 0; i < numScriptableObjects; i++)
scriptableObjects[i] = GetInstance(serializedObjects[i]);
//Unity resolves references when overwriting. We've already replaced instanceIDs in the json.
for (int i = 0; i < numScriptableObjects; i++)
JsonUtility.FromJsonOverwrite(serializedObjects[i].json, scriptableObjects[i]);
if (numScriptableObjects < numObjects)
return JsonUtility.FromJson(lastObject.json, type);
return scriptableObjects[numScriptableObjects - 1];
}
private ScriptableObject GetInstance(SerializedObject obj)
{
Type type = Type.GetType(obj.type);
if (type == null)
throw new Exception(string.Format("Could not find type '{0}'", obj.type));
ScriptableObject scriptableObject = ScriptableObject.CreateInstance(type);
string newInstanceID = scriptableObject.GetInstanceID().ToString();
string oldInstanceID = obj.instanceID;
foreach (SerializedObject serializedObject in serializedObjects)
serializedObject.UpdateInstanceID(oldInstanceID, newInstanceID);
return scriptableObject;
}
private void GetObjects(string json)
{
int level = 0;
int prev = 0;
for (int i = 0; i < json.Length; i++)
{
if (json[i] == '{')
{
level++;
if (level == 1)
prev = i;
}
if (json[i] == '}' || i == json.Length - 1)
{
level--;
if (level > 0)
continue;
string obj = json.Substring(prev, i - (prev - 1));
serializedObjects.Add(new SerializedObject(obj));
}
}
}
private void GetScriptableObjects(object obj)
{
scriptableObjects = new List<ScriptableObject>();
HashSet<object> visited = new HashSet<object>();
GetScriptableObjects(obj, visited);
}
private void GetScriptableObjects(object obj, HashSet<object> visited)
{
if (obj == null)
return;
if (visited.Contains(obj))
return;
visited.Add(obj);
FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
if (!traverse.Contains(field))
continue;
object value = field.GetValue(obj);
if (value is IList)
GetScriptableObjects(value as IList, visited);
else
GetScriptableObjects(value, visited);
}
ScriptableObject scriptableObject = obj as ScriptableObject;
if (scriptableObject != null && !scriptableObjects.Contains(scriptableObject))
scriptableObjects.Add(scriptableObject);
}
private void GetScriptableObjects(IList list, HashSet<object> visited)
{
foreach (object obj in list)
GetScriptableObjects(obj, visited);
}
private void GetTraverse(Type type)
{
traverse = new HashSet<FieldInfo>();
HashSet<Type> visited = new HashSet<Type>();
GetTraverse(type, visited);
}
private void GetTraverse(Type type, HashSet<Type> visited)
{
if (visited.Contains(type))
return;
visited.Add(type);
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
if (!IsSerialized(field))
continue;
if (!IsSerializable(field.FieldType))
continue;
traverse.Add(field);
GetTraverse(field.FieldType, visited);
}
}
private static bool IsSerialized(FieldInfo field)
{
object[] attributes = field.GetCustomAttributes(typeof(SerializeField), true);
return field.IsPublic || attributes.Length > 0;
}
private static bool IsSerializable(Type type)
{
if (typeof(ScriptableObject).IsAssignableFrom(type))
return true;
if (IsSerializableList(type))
return true;
object[] attributes = type.GetCustomAttributes(typeof(SerializableAttribute), true);
return attributes.Length > 0;
}
private static bool IsSerializableList(Type type)
{
foreach (Type t in type.GetInterfaces())
{
if (!t.IsGenericType)
continue;
if (t.GetGenericTypeDefinition() != typeof(IList<>))
continue;
if (IsSerializable(t.GetGenericArguments()[0]))
return true;
}
return false;
}
private class SerializedObject
{
public string type { get; private set; }
public string instanceID { get; private set; }
public string json { get; private set; }
public SerializedObject(object obj)
{
type = obj.GetType().AssemblyQualifiedName;
if (obj is ScriptableObject)
instanceID = (obj as ScriptableObject).GetInstanceID().ToString();
json = JsonUtility.ToJson(obj, true);
json = json.Insert(1, "\n \t\"$type\": \"" + type + "\",\n \t\"$id\": \"" + instanceID + "\",");
json = json.Replace(" ", "\t");
}
public SerializedObject(string json)
{
type = GetValue(json, "$type");
instanceID = GetValue(json, "$id");
this.json = json;
}
public void UpdateInstanceID(string oldInstanceID, string newInstanceID)
{
oldInstanceID = ": " + oldInstanceID;
newInstanceID = ": " + newInstanceID;
json = json.Replace(oldInstanceID, newInstanceID);
}
private static string GetValue(string json, string key)
{
int start = json.IndexOf(key) + key.Length + 4;
int end = json.IndexOf("\"", start);
if (end == -1)
return string.Empty;
return json.Substring(start, end - start);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment