JSON.NET: как десериализовать свойство интерфейса на основе значения объекта parent (владельца)?

У меня есть такие классы

class Holder {
    public int ObjType { get; set; }
    public List<Base> Objects { get; set; }
}

abstract class Base {
    // ... doesn't matter
}

class DerivedType1 : Base {
    // ... doesn't matter
}

class DerivedType2 : Base {
    // ... doesn't matter
}

Используя WebAPI, я хочу получить объект Holder и десериализировать его правильно. Основываясь на значении ObjType, мне нужно, чтобы свойство Objects десериализовалось либо как List<DerivedType1> (ObjType == 1), либо List<DerivedType2> (ObjType == 2).

В настоящий момент я искал SO и Интернет для лучшего подхода, но лучшее, что я нашел, - это ответ qaru.site/info/4493/.... Проблема этого решения заключается в том, что он освобождает контекст родительского объекта, поэтому я не могу узнать значение ObjType. Хорошо, я мог бы решить эту проблему, создав пользовательский JsonConverter для Holder и вспомнив значение ObjType, но все же я боюсь этой строки

serializer.Populate(jObject.CreateReader(), target);

как комментарий ниже этого ответа говорит

Новый JsonReader, созданный в методе ReadJson, не наследует никаких исходных значений конфигурации считывателя (Culture, DateParseHandling, DateTimeZoneHandling, FloatParseHandling и т.д.). Эти значения должны быть скопированы перед использованием нового JsonReader в serializer.Populate().

что для меня проблема и копирование этих значений само по себе не кажется мне чистым (что, если я что-то пропущу?)

Итак, вопрос: Есть ли лучший подход, который я пропустил для десериализации абстрактного свойства объекта на основе значения родительского свойства?

Ответы

Ответ 1

Вы на правильном пути. Вам необходимо реализовать пользовательский JsonConverter для вашего класса Holder для обработки этой ситуации, как вы предложили. Но, не волнуйтесь, можно написать конвертер таким образом, чтобы вы могли использовать оригинальные экземпляры считывателя и сериализатора, переданные в конвертер, без необходимости копировать настройки в новые экземпляры. Вот как я его написал:

class HolderConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Holder));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        Holder holder = new Holder();
        holder.ObjType = (int)jo["ObjType"];
        holder.Objects = new List<Base>();
        foreach (JObject obj in jo["Objects"])
        {
            if (holder.ObjType == 1)
                holder.Objects.Add(obj.ToObject<DerivedType1>(serializer));
            else
                holder.Objects.Add(obj.ToObject<DerivedType2>(serializer));
        }
        return holder;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Вот быстрая демонстрация:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        [
            {
                ""ObjType"" : 1,
                ""Objects"" : 
                [
                    { ""Id"" : 1, ""Foo"" : ""One"" },
                    { ""Id"" : 2, ""Foo"" : ""Two"" },
                ]
            },
            {
                ""ObjType"" : 2,
                ""Objects"" : 
                [
                    { ""Id"" : 3, ""Bar"" : ""Three"" },
                    { ""Id"" : 4, ""Bar"" : ""Four"" },
                ]
            },
        ]";

        List<Holder> list = JsonConvert.DeserializeObject<List<Holder>>(json);

        foreach (Holder holder in list)
        {
            if (holder.ObjType == 1)
            {
                foreach (DerivedType1 obj in holder.Objects)
                {
                    Console.WriteLine("Id: " + obj.Id + "  Foo: " + obj.Foo);
                }
            }
            else
            {
                foreach (DerivedType2 obj in holder.Objects)
                {
                    Console.WriteLine("Id: " + obj.Id + "  Bar: " + obj.Bar);
                }
            }
        }
    }
}

[JsonConverter(typeof(HolderConverter))]
class Holder
{
    public int ObjType { get; set; }
    public List<Base> Objects { get; set; }
}

abstract class Base
{
    public int Id { get; set; }
}

class DerivedType1 : Base
{
    public string Foo { get; set; }
}

class DerivedType2 : Base
{
    public string Bar { get; set; }
}

Вывод:

Id: 1  Foo: One
Id: 2  Foo: Two
Id: 3  Bar: Three
Id: 4  Bar: Four