S.Richmond S.Richmond - 2 months ago 17
C# Question

How can I custom serialize a GameObject reference by its GUID?

I'm writing a custom serializer for our Unity3D game using JSON .NET. We can have some fields of type GameObject, which is a Unity3D internal class and will reference an asset resource in the game. We need to be able to serialize GameObject property by only its' GUID string. Using this information we can utilize Unity3D's AssetDatabase to retrieve and load the asset into the property.

public class Blueprint {
public IList<IComponent> Components = new List<IComponent>();
}

public class UnityPrefabs : IComponent {
public GameObject ViewPrefab;
public GameObject ModelPrefab;
}


The methods I'm using to serialize with JSON .NET:

public void Serialize(Blueprint blueprint) {
blueprintData = JsonConvert.SerializeObject(blueprint, Formatting.Indented,
new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
Converters = new List<JsonConverter>() { new UnityPrefabConverter() },
//ContractResolver = new ConverterContractResolver()
});
}

public Blueprint Deserialize() {
var blueprint = string.IsNullOrEmpty(blueprintData)
? new Blueprint() : JsonConvert.DeserializeObject<Blueprint>(blueprintData,
new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
//TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
NullValueHandling = NullValueHandling.Include,
Converters = new List<JsonConverter>() { new UnityPrefabConverter() },
});
return blueprint;
}


I did make an attempt to write a custom converter, but the Write() method isn't being called whilst only the Read() is:

public class UnityPrefabConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var go = value as GameObject;
var guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(go));

writer.WriteValue(guid);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Debug.Log("Reading json");
if (existingValue == null) return null;

return AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(existingValue as string));
}

public override bool CanConvert(Type objectType)
{
return (objectType == typeof(GameObject));
}
}


The only oddity I can find right now is that it isn't serializing the type in the JSON for the GameObject fields:


{\r\n \"$type\": \"CubeBasic.Model.UnityPrefabs, Assembly-CSharp\",\r\n
\ \"ViewPrefab\": \"\",\r\n \"ModelPrefab\": null\r\n }\r\n
\ ]\r\n }\r\n}"

dbc dbc
Answer

Your converter is not called because, during serialization, the objectType is the actual type of GameObject being serialized and thus is a subtype of GameObject. Modify CanConvert to use Type.IsAssignableFrom(Type):

public override bool CanConvert(Type objectType)
{
    return typeof(GameObject).IsAssignableFrom(objectType);
}

Also, in your ReadJson, you probably do not want to check whether existingValue is null, instead you want to do:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    Debug.Log("Reading json");
    if (reader.TokenType == JsonToken.Null)
        return null;
    var guid = (string)JToken.Load(reader); // Load the GUID value

    return AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(guid));
}

existingValue is a reference to whatever POCO was pre-allocated in the constructor for the containing object.

Since you have written your own JsonConverter for GameObject, the object types will not be written unless you explicitly do so in the converter. However, since you're not actually deserializing the object, but are only fetching it from some database, I don't think that's necessary.