【问题标题】:NewtonSoft Inheretance deserialization of an JSON array Without altering the JSON不改变 JSON 的 JSON 数组的 NewtonSoft 继承反序列化
【发布时间】:2021-01-20 14:36:36
【问题描述】:

我有一个简单的用例,我想反序列化一个 JSON,它基本上是一个项目数组,项目并不相同,但它们都共享一个基类。

更新: 我的技术限制:

  • 我从 webhook 接收 JSON,但无法更改序列化代码或注入 源 JSON 中的任何标记
  • Type 属性是唯一用于在我想要反序列化到的派生类之间进行对应的信息
  • 需要强类型实例而不是 dynamic 实例
  • 这些类在一个程序集中,我无法添加任何 Json 注释

代码如下:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
                    
public class Program
{
    public static void Main()
    {
        var json = @" [{""Type"":0},{""Name"":""Derived"",""Type"":1}]";
        
        var deserializedInstances = JsonConvert.DeserializeObject<List<BaseClass>>(json);
        foreach(var e  in deserializedInstances) {
            if(e is BaseClass baseClass) 
            {
                Console.WriteLine("Base Class , Type = {0}", baseClass.Type);
            }else if(e is DerviedClass derivedClass) 
            {
                Console.WriteLine("Derived Class , Type = {0}, Name = {1}", derivedClass.Type, derivedClass.Name);
            }
        }
        // Output 
        // Base Class , Type = 0
        // Base Class , Type = 0
        
        
    }
    
    public class BaseClass   
    {
        public virtual int Type  =>0;
    }
    public class DerviedClass:  BaseClass   
    {
        public string  Name  {get; set;}
        public override int Type =>1;
    }
}

所以这段代码会产生这样的输出:

        // Base Class , Type = 0
        // Base Class , Type = 0

但就我而言,我想要派生类的实例。

        // Base Class , Type = 0
        // Base Class , Type = 1, Name = "Derived"

性能而言,实现这一目标的最佳方法是什么?

【问题讨论】:

  • 您正在反序列化到 BaseClass... newtonsoft 应该如何知道数据是用于派生类的?您可能可以反序列化为派生类,如果名称为空,则其 BaseClass
  • 检查这个解决方案:stackoverflow.com/questions/8513042/…
  • 可以反序列化到dynamic ...然后从那里继续。
  • 这只是一个 PoC。显示的代码远非“生产级”。我很少,几乎从不必须使用动态,所以肯定有优化算法来获得你想要的强类型,我不知道。

标签: c# json.net deserialization


【解决方案1】:

正如 Jawad 指出的那样,您正在反序列化为 BaseClass,因此对象也将是 BaseClass 类型,并且不会超出此范围。

你想做的是类似于这个答案:Json.net serialize/deserialize derived types?

简短的回答是您必须在反序列化时考虑设置,更具体地说是类型名称处理。从答案复制:

Base object1 = new Base() { Name = "Object1" };
Derived object2 = new Derived() { Something = "Some other thing" };
List<Base> inheritanceList = new List<Base>() { object1, object2 };

JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
string Serialized = JsonConvert.SerializeObject(inheritanceList, settings);
List<Base> deserializedList = JsonConvert.DeserializeObject<List<Base>>(Serialized, settings);

这将允许反序列化为派生类等。也可以在这里找到一个示例:https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm

编辑:就性能而言,我不确定是否有更好的方法可以产生您正在寻找的相同结果。

【讨论】:

  • 是的,我看到了,但正如我上面所说,就我而言,我无法访问序列化部分,我只有 JSON 文本和可用作鉴别器的 Type 属性派生类型的
  • @BelahceneBenzaraTahar 然后你需要在你的问题范围内定义它。为了使其工作,您需要访问 JSON 并注入 "$type": 并将该类型的范围定义到您的类(安全风险)。否则,您还可以创建自己的转换器来分配哪个类接收哪些数据,例如这里的示例:stackoverflow.com/questions/6348215/…
  • 我更新了问题,谢谢我会探索你发送的链接
  • 我正在尝试使用自定义转换器使其工作,但它似乎不起作用!这是dotnetfiddle dotnetfiddle.net/UcH36B
  • @BelahceneBenzaraTahar 您的转换器正在按预期工作。您遇到的问题更多是在循环对象时区分BaseClassDerivedClass。有几种方法可以解决这个问题,Fildor 指出了一种方法。另一个是循环并注意对象as派生类,然后检查对象或只是反序列化为objects列表(类似于dynamicdynamic item = serializer.Deserialize&lt;object&gt;(getString); string name = item["name"];
【解决方案2】:

绕道dynamic 强类型化:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

public class Program
{
    public static void Main()
    {
        var a = new BaseClass(){ Type = 0 };
        var b = new DerivedClass(){ Type = 1, Name = "Hello" };
        
        var list = new List<BaseClass>(){a,b};
        var json = JsonConvert.SerializeObject(list);
        Console.WriteLine(json);
        
        var intermediate = JsonConvert.DeserializeObject<List<dynamic>>(json);
        var result = new List<object>();
        foreach( dynamic item in intermediate )
        {
            // Of course, you surely could optimize the conversion:
            if( item.Type == 0 ) result.Add( new BaseClass(){Type = item.Type});
            if( item.Type == 1 ) result.Add( new DerivedClass(){Type= item.Type, Name= item.Name});
        }
        
        Console.WriteLine($"[{string.Join(", ",result)}]");
    }
}

public class BaseClass
{
    public int Type  {get; set;}
}
public class DerivedClass:  BaseClass   
{
    public string  Name  {get; set;}
}

fiddle 上产生输出:

[{"Type":0},{"Name":"Hello","Type":1}] [BaseClass,DerviedClass]

请注意,这只是概念验证。当然,你需要加强并找到一些体面的算法来从动态类型转换为你想要的强类型。


更新

这个小提琴展示了一些改进许多派生类工作量的可能性:https://dotnetfiddle.net/zECBx5

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var a = new BaseClass(){ Type = 0 };
        var b = new DerivedClass(){ Type = 1, Name = "Hello" };
        
        var list = new List<BaseClass>(){a,b};
        var json = JsonConvert.SerializeObject(list);
        Console.WriteLine(json);
        
        var intermediate = JsonConvert.DeserializeObject<List<dynamic>>(json);
        
        var result = Construct( intermediate );
        
        Console.WriteLine($"[{string.Join(", ",result.Select(x => x?.ToString() ?? "NULL"))}]");
    }
    
    public static List<object> Construct( List<dynamic> items )
    {
        var result = new List<object>();
        Console.WriteLine( $"Processing {items.Count} dynamic items" );
        foreach( dynamic item in items )
        {
            result.Add(Construct( item ));
        }
        return result;
    }
    
    private static Dictionary<int, Func<dynamic, object>> factoryMap = new () {
        {0 , Construct<BaseClass>},
        {1 , Construct<DerivedClass>},
    };
    
    public static object Construct( dynamic item )
    {
        Console.WriteLine($"Item Type = {item.Type}");
        object result = null;
        result = factoryMap[(int)item.Type](item);
        return result;
    }
    
    public static TResult Construct<TResult>( dynamic item ) where TResult: class, new()
    {
        Console.WriteLine($"Constructing a {typeof(TResult).ToString()}");
        TResult result = new();
        foreach( var property in result.GetType().GetProperties() )
        {
            JObject jo = item as JObject;
            var propVal = jo.Property(property.Name).ToObject(property.PropertyType);
            Console.WriteLine($"Setting property {property.Name} to value {propVal}");
            property.SetValue( result, propVal );
        }
        return result;
    }
}

    public class BaseClass
    {
        public int Type  {get; set;}
    }
    public class DerivedClass:  BaseClass   
    {
        public string  Name  {get; set;}
    }

【讨论】:

  • 您的解决方案有效,虽然我采用了转换器方法,但这是最接近我想要的解决方案,我不想接受我自己的解决方案,所以...
【解决方案3】:

自定义转换器是我一直在寻找的解决方案:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

                    
public class Program
{
    public static void Main()
    {
        var json = @" [{""Type"":0},{""Name"":""Derived"",""Type"":1}]";
        
        var deserializedInstances = JsonConvert.DeserializeObject<List<BaseClass>>(json,
                new JsonSerializerSettings() 
                { 
                    Converters = { new CustomConverter() }
                });
        foreach(var e  in deserializedInstances) {
            if(e is BaseClass baseClass && e.Type == 0) 
            {
                Console.WriteLine("Base Class , Type = {0}", baseClass.Type);
            } 
            if(e is DerviedClass derivedClass) 
            {
                Console.WriteLine("Derived Class , Type = {0}, Name = {1}", derivedClass.Type, derivedClass.Name);
            }
        }   
        // output
        // Base Class , Type = 0
        // Derived Class , Type = 1, Name = Derived
    }
    
    public class BaseClass   
    {
        public  int Type {get; set;}
    }
    public class DerviedClass:  BaseClass   
    {
        public string  Name  {get; set;}
    }
    
    public class CustomConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(BaseClass).IsAssignableFrom(objectType) || typeof(BaseClass) == objectType;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject jo = JObject.Load(reader);
            if (jo["Type"].Value<int>() == 0)
                return new BaseClass() { Type = jo["Type"].Value<int>()}; // avoid stack overflow

           if (jo["Type"].Value<int>() == 1)
               return jo.ToObject<DerviedClass>(serializer);
            return null;
        }

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

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

【讨论】:

    猜你喜欢
    • 2021-10-13
    • 2017-10-16
    • 1970-01-01
    • 1970-01-01
    • 2013-06-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多