0
\$\begingroup\$

I'm using JsonConvert.DeserializeObject to deserialize animals from the Animal list, but unfortunately they get deserialized as Animals instead of their derived classes (Cat and Dog). As you can see, the needed information is being serialized ("$type"). Does anyone know how to solve this?

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using UnityEngine;

public class SerializationExample : MonoBehaviour
{
    void Start()
    {
        var dog1 = ScriptableObject.CreateInstance<Dog>();
        dog1.age = 6;
        dog1.breed = "Bulldog";

        var cat1 = ScriptableObject.CreateInstance<Cat>();
        cat1.age = 7;
        cat1.lives = 5;

        var zoo1 = ScriptableObject.CreateInstance<Zoo>();
        zoo1.animals = new Animal[] { dog1, cat1 };

        JsonSerializerSettings settings = new() { Formatting = Formatting.Indented, TypeNameHandling = TypeNameHandling.Auto };
        settings.Converters.Add(new ScriptableObjectCreationConverter<Cat>());
        settings.Converters.Add(new ScriptableObjectCreationConverter<Dog>());
        settings.Converters.Add(new ScriptableObjectCreationConverter<Animal>());
        settings.Converters.Add(new ScriptableObjectCreationConverter<Zoo>());

        string json = JsonConvert.SerializeObject(zoo1, settings);
        Debug.Log(json);

        /*
        {
          "animals": [
            {
              "$type": "Dog, Project",
              "breed": "Bulldog",
              "age": 6,
              "name": "",
              "hideFlags": 0
            },
            {
              "$type": "Cat, Project",
              "lives": 5,
              "age": 7,
              "name": "",
              "hideFlags": 0
            }
          ],
          "name": "",
          "hideFlags": 0
        }
        */

        zoo1 = JsonConvert.DeserializeObject<Zoo>(json, settings);
        Debug.Log(zoo1.animals[0] is Dog); // returning false instead of true!
        Debug.Log(zoo1.animals[1] is Cat); // returning false instead of true!
    }
}

public class Zoo : ScriptableObject
{
    public Animal[] animals;
}

public class Animal : ScriptableObject
{
    public int age;
}

public class Dog : Animal
{
    public string breed;
}

public class Cat : Animal
{
    public int lives;
}

public class ScriptableObjectCreationConverter<T> : CustomCreationConverter<T> where T : ScriptableObject
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType;
    }

    public override T Create(Type objectType)
    {
        if (typeof(T) == objectType) return (T)ScriptableObject.CreateInstance<T>();
        return default;
    }
}
```
\$\endgroup\$
1
  • \$\begingroup\$ Myself, I serialize as XML when I need polymorphic types. Is that an option in your situation? \$\endgroup\$
    – DMGregory
    Commented Jun 29 at 1:50

1 Answer 1

0
\$\begingroup\$

I found a way. Here's the code:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using UnityEngine;

public class SerializationExample : MonoBehaviour
{
    void Start()
    {
        var dog1 = ScriptableObject.CreateInstance<Dog>();
        dog1.age = 6;
        dog1.breed = "Bulldog";

        var cat1 = ScriptableObject.CreateInstance<Cat>();
        cat1.age = 7;
        cat1.lives = 5;

        var zoo1 = ScriptableObject.CreateInstance<Zoo>();
        zoo1.animals = new Animal[] { dog1, cat1 };

        JsonSerializerSettings settings = new() { Formatting = Formatting.Indented };
        settings.Converters.Add(new ScriptableObjectConverter<Cat>());
        settings.Converters.Add(new ScriptableObjectConverter<Dog>());
        settings.Converters.Add(new ScriptableObjectConverter<Animal>());
        settings.Converters.Add(new ScriptableObjectConverter<Zoo>());

        string json = JsonConvert.SerializeObject(zoo1, settings);
        Debug.Log(json);

        /*
        {
          "$type": "Zoo",
          "animals": [
            {
              "$type": "Dog",
              "breed": "Bulldog",
              "age": 6
            },
            {
              "$type": "Cat",
              "lives": 5,
              "age": 7
            }
          ]
        }
        */

        zoo1 = JsonConvert.DeserializeObject<Zoo>(json, settings);
        Debug.Log(zoo1.animals[0] is Dog); // finally returning true
        Debug.Log(zoo1.animals[1] is Cat); // finally returning true
    }
}

public class Zoo : ScriptableObject
{
    public Animal[] animals;
}

public class Animal : ScriptableObject
{
    public int age;
}

public class Dog : Animal
{
    public string breed;
}

public class Cat : Animal
{
    public int lives;
}

public class ScriptableObjectConverter<T> : JsonConverter where T : ScriptableObject
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var jsonObject = new JObject
        {
            { "$type", JToken.FromObject(value.GetType().FullName, serializer) } // alternatively AssemblyQualifiedName
        };

        // Serialize properties
        foreach (var prop in value.GetType().GetProperties())
        {
            if (prop.Name == "name" || prop.Name == "hideFlags") continue;
            if (prop.CanRead) jsonObject.Add(prop.Name, JToken.FromObject(prop.GetValue(value), serializer));
        }

        // Serialize fields
        foreach (var field in value.GetType().GetFields())
        {
            jsonObject.Add(field.Name, JToken.FromObject(field.GetValue(value), serializer));
        }

        jsonObject.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var typeName = jsonObject["$type"]?.ToString();
        if (typeName == null) throw new JsonSerializationException("Missing $type field.");

        var type = Type.GetType(typeName);
        if (type == null) throw new JsonSerializationException($"Unknown type: {typeName}");

        var obj = ScriptableObject.CreateInstance(type);
        serializer.Populate(jsonObject.CreateReader(), obj);
        return obj;
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .