-
-
Save Hello-Meow/306520e10632109a54e64d1e23c7b355 to your computer and use it in GitHub Desktop.
A serializer using Unity's JsonUtility that also serializes referenced ScriptableObjects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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