JSON 数组的 NewtonSoft 继承反序列化而不改变 JSON

NewtonSoft Inheretance deserialization of an JSON array Without altering the JSON

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

更新: 我的技术限制:

代码如下:

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

但在我的例子中,我想要派生的实例 class。

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

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

正如 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

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

绕过 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]

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


更新

这 fiddle 显示了一些改进许多 Derived 类 的努力的可能性: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;}
    }

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

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();
        }
   }
}