JSON.NET:如何根据父(持有者)对象值反序列化接口属性?

Zok*_*oka 23 c# json.net asp.net-web-api json-deserialization

我有这样的课程

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
}
Run Code Online (Sandbox Code Playgroud)

使用WebAPI我想要接收对象Holder并正确反序列化它.基于ObjType值,我需要Objects将要反序列化的属性作为List<DerivedType1>(ObjType == 1)或List<DerivedType2>(ObjType == 2).

目前我搜索了SO和互联网以获得最佳方法,但我找到的最好的是这个答案/sf/answers/562189841/.这个解决方案的问题是,它松散了父对象的上下文,所以我找不到它的值ObjType.OK,我可以创建自定义解决它JsonConverter用于Holder和remebering的ObjType价值,但我还是很affraid这行

serializer.Populate(jObject.CreateReader(), target);
Run Code Online (Sandbox Code Playgroud)

正如下面的评论这个答案所说

在ReadJson方法中创建的新JsonReader不会继承任何原始读者的配置值(Culture,DateParseHandling,DateTimeZoneHandling,FloatParseHandling等等).在serializer.Populate()中使用新的JsonReader之前,应复制这些值.

这对我来说是个问题并且我自己复制这些值对我来说似乎并不干净(如果我错过了什么怎么办?)

所以问题是:是否有更好的方法我错过了基于父属性值反序列化抽象对象属性?

Bri*_*ers 46

你走在正确的轨道上.您需要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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个快速演示:

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; }
}
Run Code Online (Sandbox Code Playgroud)

输出:

Id: 1  Foo: One
Id: 2  Foo: Two
Id: 3  Bar: Three
Id: 4  Bar: Four
Run Code Online (Sandbox Code Playgroud)

  • 这是一个简洁的解决方案,比复制JsonReader的属性更具有前瞻性.一个小问题:如果将转换器应用于类而不是属性,则存在递归和堆栈溢出的可能性. (6认同)