Since GameObjects cannot be serialized I wrote a class that holds the name of the GameObject so that it can find it by the name. However I am getting this error during deserialization and I can't figure out why.
JsonSerializationException: Type specified in JSON 'GameObjectReferencer, SaveSystem, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not compatible with 'UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. Path 'wrappedSaveDatas.$values[4].savedObject.other.$type', line 338, position 55.
This is my Custom converter:
public class GameObjectFromReferenceConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{ return objectType == typeof(GameObjectReferencer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ throw new Exception("GameObjectFromReferenceConverter should only be used when loading");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
GameObjectReferencer goRef = (GameObjectReferencer)reader.Value;
return ((GameObjectReferencer)goRef).GetGameObject();
}
}
This is my Referencer class:
public class GameObjectReferencer
{
public string targetGameObjectName;
public GameObjectReferencer(string gameObjectName)
{
this.targetGameObjectName = gameObjectName;
}
public GameObject GetGameObject()
{
GameObject go = GameObject.Find(targetGameObjectName);
if (go == null)
throw new System.Exception("Can't find object with the name " + targetGameObjectName);
return go;
}
public override string ToString()
{
return "GameObjectReference of \"" + targetGameObjectName+ "\"";
}
public static GameObjectReferencer GenerateReference(GameObject gameObject)
{
return new GameObjectReferencer(gameObject.name);
}
}
Your GameObjectReferencer
is an example of a DTO (data transfer object)
a data transfer object (DTO) is an object that carries data between processes.
Since serialization and deserialization need to have a consistent format, that generally means that your DTO must get used for both. Thus your converter should look something like the following:
public class GameObjectFromReferenceConverter : JsonConverter
{
public override bool CanConvert(Type objectType) =>
typeof(GameObject).IsAssignableFrom(objectType);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
serializer.Serialize(writer, GameObjectReferencer.GenerateReference((GameObject)value));
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
serializer.Deserialize<GameObjectReferencer>(reader)?.GetGameObject();
}
// Serialization DTO class for GameObject and derived types.
class GameObjectReferencer
{
public string targetGameObjectName { get; set; }
public GameObjectReferencer(string targetGameObjectName) => this.targetGameObjectName = targetGameObjectName;
public GameObject GetGameObject() =>
GameObject.Find(targetGameObjectName) ?? throw new System.Exception("Can't find object with the name " + targetGameObjectName);
public static GameObjectReferencer GenerateReference(GameObject gameObject) => new GameObjectReferencer(gameObject.name);
public override string ToString() => "GameObjectReference of \"" + targetGameObjectName+ "\"";
}
And you must also use the converter in settings for both serialization and deserialization, e.g. like so:
var settings = new JsonSerializerSettings
{
Converters = { new GameObjectFromReferenceConverter() },
// Other settings as required, e.g.:
TypeNameHandling = TypeNameHandling.Auto,
};
Notes:
When serializing with a converter, the converter must take care of all aspects of serialization and deserialization, including serialization and deserialization of $type
metadata. Thus converters do not necessarily play well with Json.NET's TypeHandHandling
setting.
When deserializing, the objectType
passed to JsonConverter.CanConvert(objectType)
will be the declared type of the object to be deserialized. Since the declared type of any GameObject
reference will typically be GameObject
or some subtype, in CanConvert
you need to check that the type is assignable from GameObject
not GameObjectReferencer
.
When serializing the objectType
will be the actual, concrete type being serialized, which will, again, be some subtype of GameObject
.
Inside ReadJson()
the value of JsonReader.Value
will be the value of the current json token. When the current token is JsonToken.StartObject
(i.e. positioned on the {
token), JsonReader.Value
will be null
.
For Json.NET to successfully deserialize a type using a parameterized constructor, the constructor argument names must match the property names (here targetGameObjectName
).
When using TypeNameHandling
, do take note of this caution from the Newtonsoft docs:
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.
For a similar converter using GUIDs instead of names, see How can I custom serialize a GameObject reference by its GUID?.
Mockup fiddle here.