Search code examples
c#serializationpolymorphism.net-6.0json-deserialization

c# deserialize nested polymorphic object / derived classes


In order to deserialize derived object it is possible to use CustomCreationConverter and Discriminators. So I managed to get this working for the SkillEffect base class but now I need the same approach for a Skill base class as well. How can I use the SkillEffectConverter within the SkillConverter when deserializing a ProjectileSkill? If this is not possible with Newtonsoft, are there other libraries approaches to achieve this?

    internal class Program
    {
        static void Main(string[] args)
        {
            part1();
            part2();
        }

        static void part1()
        {
            SkillEffect resourceEffect = new ResourceSkillEffect(3);

            var json = JsonConvert.SerializeObject(resourceEffect, new SkillEffectConverter());
            Console.WriteLine(json);

            var effect = JsonConvert.DeserializeObject<SkillEffect>(json, new SkillEffectConverter());
            effect.Apply();
        }

        static void part2()
        {
            Skill projSkill = new ProjectileSkill();

            projSkill.effects.Add(new ResourceSkillEffect(1));
            projSkill.effects.Add(new ResourceSkillEffect(2));

            var json = JsonConvert.SerializeObject(projSkill, new SkillConverter());
            Console.WriteLine(json);

            var skill = JsonConvert.DeserializeObject<Skill>(json, new SkillConverter());
            Console.WriteLine(skill);

            Console.Read();
        }
    }

    public abstract class Skill
    {
        public abstract SkillDiscriminator Type { get; }
        public List<SkillEffect> effects = new();
    }

    public class ProjectileSkill : Skill
    {
        public override SkillDiscriminator Type => SkillDiscriminator.ProjectileSkill;
    }

    public enum SkillDiscriminator
    {
        ProjectileSkill
    }

    public class SkillConverter : CustomCreationConverter<Skill>
    {
        private SkillDiscriminator _currentObjectType;

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jobj = JObject.ReadFrom(reader);
            _currentObjectType = jobj["Type"].ToObject<SkillDiscriminator>();
            return base.ReadJson(jobj.CreateReader(), objectType, existingValue, serializer);
        }

        public override Skill Create(Type objectType)
        {
            return _currentObjectType switch
            {
                SkillDiscriminator.ProjectileSkill => new ProjectileSkill(),
                _ => throw new NotImplementedException(),
            };
        }
    }

    public abstract class SkillEffect
    {
        public abstract SkillEffectDiscriminator Type { get; }
        public abstract void Apply();
    }

    public class ResourceSkillEffect : SkillEffect
    {
        public override SkillEffectDiscriminator Type => SkillEffectDiscriminator.ResourceSkillEffect;

        public ResourceSkillEffect() { }
        public ResourceSkillEffect(int value)
        {
            this.value = value;
        }

        public int value;

        public override void Apply()
        {
            Console.WriteLine($"ResourceSkillEffect: value={value}");
        }
    }

    public enum SkillEffectDiscriminator
    {
        ResourceSkillEffect
    }


    public class SkillEffectConverter : CustomCreationConverter<SkillEffect>
    {
        private SkillEffectDiscriminator _currentObjectType;

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jobj = JObject.ReadFrom(reader);
            _currentObjectType = jobj["Type"].ToObject<SkillEffectDiscriminator>();
            return base.ReadJson(jobj.CreateReader(), objectType, existingValue, serializer);
        }

        public override SkillEffect Create(Type objectType)
        {
            return _currentObjectType switch
            {
                SkillEffectDiscriminator.ResourceSkillEffect => new ResourceSkillEffect(1),
                _ => throw new NotImplementedException(),
            };
        }
    }

Edit 1: I updated the code with a full example. Function part1() works as expected and part2() crashes as expected.


Solution

  • I just saw that I can pass multiple converters to the JsonConvert.DeserializeObject function which works.

    var skill = JsonConvert.DeserializeObject<Skill>(
                        json, 
                        new SkillConverter(),
                        new SkillEffectConverter());