JSON 数组的 NewtonSoft 继承反序列化而不改变 JSON
NewtonSoft Inheretance deserialization of an JSON array Without altering the JSON
我有一个简单的用例,我想反序列化一个 JSON,它基本上是一个项目数组,项目不相同,但它们都共享一个基础 class.
更新:
我的技术限制:
- 我从 webhook 收到 JSON 并且无法更改序列化代码或注入
来源 JSON
中的任何标记
Type
属性是我要反序列化的派生class与 做对应的唯一信息
- 需要强类型实例而不是
dynamic
实例
- classes 在程序集中,我无法添加任何 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
但在我的例子中,我想要派生的实例 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();
}
}
}
我有一个简单的用例,我想反序列化一个 JSON,它基本上是一个项目数组,项目不相同,但它们都共享一个基础 class.
更新: 我的技术限制:
- 我从 webhook 收到 JSON 并且无法更改序列化代码或注入 来源 JSON 中的任何标记
Type
属性是我要反序列化的派生class与 做对应的唯一信息
- 需要强类型实例而不是
dynamic
实例 - classes 在程序集中,我无法添加任何 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
但在我的例子中,我想要派生的实例 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();
}
}
}