具有 IEnumerable<ISomeInterface> 类型属性的 NewtonSoft.Json 序列化和反序列化类

Posted

技术标签:

【中文标题】具有 IEnumerable<ISomeInterface> 类型属性的 NewtonSoft.Json 序列化和反序列化类【英文标题】:NewtonSoft.Json Serialize and Deserialize class with property of type IEnumerable<ISomeInterface> 【发布时间】:2012-07-30 01:50:36 【问题描述】:

我正在尝试移动一些代码以使用 ASP.NET MVC Web API 生成的 Json 数据而不是 SOAP Xml。

我在序列化和反序列化以下类型的属性时遇到了问题:

IEnumerable<ISomeInterface>.

这是一个简单的例子:

public interface ISample
  int SampleId  get; set; 

public class Sample : ISample
  public int SampleId  get; set; 

public class SampleGroup
  public int GroupId  get; set; 
  public IEnumerable<ISample> Samples  get; set; 
 

我可以通过以下方式轻松序列化 SampleGroup 的实例:

var sz = JsonConvert.SerializeObject( sampleGroupInstance );

但是对应的反序列化失败了:

JsonConvert.DeserializeObject<SampleGroup>( sz );

带有此异常消息:

“无法创建 JsonSerializationExample.ISample 类型的实例。类型是接口或抽象类,无法实例化。”

如果我派生一个 JsonConverter,我可以如下装饰我的属性:

[JsonConverter( typeof (SamplesJsonConverter) )]
public IEnumerable<ISample> Samples  get; set; 

这是 JsonConverter:

public class SamplesJsonConverter : JsonConverter
  public override bool CanConvert( Type objectType )
    return ( objectType == typeof (IEnumerable<ISample>) );
  

  public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
    var jA = JArray.Load( reader );
    return jA.Select( jl => serializer.Deserialize<Sample>( new JTokenReader( jl ) ) ).Cast<ISample>( ).ToList( );
  

  public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
    ... What works here?
  

此转换器解决了反序列化问题,但我不知道如何编写 WriteJson 方法以使序列化再次工作。

有人可以帮忙吗?

这是解决问题的“正确”方法吗?

【问题讨论】:

除了下面的答案之外,您还可以覆盖CanWrite 并返回false。见How to use default serialization in a custom JsonConverter。 @AndyDBell - 您可能需要考虑在此线程中接受其中一个答案作为已接受的答案。已经有一些非常好的答案。您可以使用投票按钮下方的复选标记将最有帮助的答案标记为已接受的答案。 虽然cuongle 的answer 很棒,并且很好地利用了Json.NET 自己的CustomCreationConverter&lt;T&gt;,但要将转换器应用于收集项使用[JsonProperty (ItemConverterType = typeof(SamplesJsonConverter))],如this answer 所示到Serialize a container of enums as strings using JSON.net。 【参考方案1】:

您不需要使用JsonConverterAttribute,只需保持模型干净并改用CustomCreationConverter,代码更简单:

public class SampleConverter : CustomCreationConverter<ISample>

    public override ISample Create(Type objectType)
    
        return new Sample();
    

然后:

var sz = JsonConvert.SerializeObject( sampleGroupInstance );
JsonConvert.DeserializeObject<SampleGroup>( sz, new SampleConverter());

文档:Deserialize with CustomCreationConverter

【讨论】:

【参考方案2】:

考虑到在大多数情况下,您不希望提供整个数据合约的类型,而只希望提供包含抽象或接口或其列表的类型;并且考虑到这些实例在您的数据实体中非常罕见且易于识别,最简单和最不冗长的方法是使用

[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Objects)]
public IEnumerable<ISomeInterface> Items  get; set; 

作为包含可枚举/列表/集合的属性的属性。 这将仅针对该列表,并且仅附加包含对象的类型信息,如下所示:


  "Items": [
    
      "$type": "Namespace.ClassA, Assembly",
      "Property": "Value"
    ,
    
      "$type": "Namespace.ClassB, Assembly",
      "Property": "Value",
      "Additional_ClassB_Property": 3
    
  ]

干净、简单,并且位于引入数据模型复杂性的地方,而不是隐藏在某个转换器中。

【讨论】:

【参考方案3】:

我通过为 JsonSerializerSettings 使用称为 TypeNameHandling.All

的特殊设置解决了这个问题

TypeNameHandling 设置包括序列化 JSON 时的类型信息和读取类型信息以便在反序列化 JSON 时创建创建类型

序列化:

var settings = new JsonSerializerSettings  TypeNameHandling = TypeNameHandling.All ;
var text = JsonConvert.SerializeObject(configuration, settings);

反序列化:

var settings = new JsonSerializerSettings  TypeNameHandling = TypeNameHandling.All ;
var configuration = JsonConvert.DeserializeObject<YourClass>(json, settings);

YourClass 类可能有任何类型的基类型字段,并且会被正确序列化。

【讨论】:

请注意,启用TypeNameHandling 会带来安全风险,详情请参阅TypeNameHandling caution in Newtonsoft Json 和External json vulnerable because of Json.Net TypeNameHandling auto?。如果ISample总是反序列化为Sample,最好使用CustomCreationConverter&lt;T&gt;【参考方案4】:

有这个:

public interface ITerm

    string Name  get; 


public class Value : ITerm...

public class Variable : ITerm...

public class Query

   public IList<ITerm> Terms  get; 
...

我管理了实现该功能的转换技巧:

public class TermConverter : JsonConverter

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        var field = value.GetType().Name;
        writer.WriteStartObject();
        writer.WritePropertyName(field);
        writer.WriteValue((value as ITerm)?.Name);
        writer.WriteEndObject();
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    
        var jsonObject = JObject.Load(reader);
        var properties = jsonObject.Properties().ToList();
        var value = (string) properties[0].Value;
        return properties[0].Name.Equals("Value") ? (ITerm) new Value(value) : new Variable(value);
    

    public override bool CanConvert(Type objectType)
    
        return typeof (ITerm) == objectType || typeof (Value) == objectType || typeof (Variable) == objectType;
    

它允许我在 JSON 中序列化和反序列化,例如:

string JsonQuery = "\"Terms\":[\"Value\":\"This is \",\"Variable\":\"X\",\"Value\":\"!\"]";
...
var query = new Query(new Value("This is "), new Variable("X"), new Value("!"));
var serializeObject = JsonConvert.SerializeObject(query, new TermConverter());
Assert.AreEqual(JsonQuery, serializeObject);
...
var queryDeserialized = JsonConvert.DeserializeObject<Query>(JsonQuery, new TermConverter());

【讨论】:

【参考方案5】:

json.net 提供的开箱即用支持非常简单,您只需在序列化和反序列化时使用以下 JsonSettings:

JsonConvert.SerializeObject(graph,Formatting.None, new JsonSerializerSettings()

    TypeNameHandling =TypeNameHandling.Objects,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
);

对于反序列化使用下面的代码:

JsonConvert.DeserializeObject(Encoding.UTF8.GetString(bData),type,
    new JsonSerializerSettings()TypeNameHandling = TypeNameHandling.Objects
);

只需记下 JsonSerializerSettings 对象初始化器,这对您很重要。

【讨论】:

请注意,启用TypeNameHandling 会带来安全风险,详情请参阅TypeNameHandling caution in Newtonsoft Json 和External json vulnerable because of Json.Net TypeNameHandling auto?。如果ISample总是反序列化为Sample,最好使用CustomCreationConverter&lt;T&gt;【参考方案6】:

很好的解决方案,谢谢! 我以 AndyDBell 的问题和 Cuong Le 的回答构建了一个具有两个不同接口实现的示例:

public interface ISample

    int SampleId  get; set; 


public class Sample1 : ISample

    public int SampleId  get; set; 
    public Sample1()  



public class Sample2 : ISample

    public int SampleId  get; set; 
    public String SampleName  get; set; 
    public Sample2()  


public class SampleGroup

    public int GroupId  get; set; 
    public IEnumerable<ISample> Samples  get; set; 


class Program

    static void Main(string[] args)
    
        //Sample1 instance
        var sz = "\"GroupId\":1,\"Samples\":[\"SampleId\":1,\"SampleId\":2]";
        var j = JsonConvert.DeserializeObject<SampleGroup>(sz, new SampleConverter<Sample1>());
        foreach (var item in j.Samples)
        
            Console.WriteLine("id:0", item.SampleId);
        
        //Sample2 instance
        var sz2 = "\"GroupId\":1,\"Samples\":[\"SampleId\":1, \"SampleName\":\"Test1\",\"SampleId\":2, \"SampleName\":\"Test2\"]";
        var j2 = JsonConvert.DeserializeObject<SampleGroup>(sz2, new SampleConverter<Sample2>());
        //Print to show that the unboxing to Sample2 preserved the SampleName's values
        foreach (var item in j2.Samples)
        
            Console.WriteLine("id:0 name:1", item.SampleId, (item as Sample2).SampleName);
        
        Console.ReadKey();
    

以及 SampleConverter 的通用版本:

public class SampleConverter<T> : CustomCreationConverter<ISample> where T: new ()

    public override ISample Create(Type objectType)
    
        return ((ISample)new T());
    

【讨论】:

【参考方案7】:

我得到了这个工作:

显式转换

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
                                    JsonSerializer serializer)
    
        var jsonObj = serializer.Deserialize<List<SomeObject>>(reader);
        var conversion = jsonObj.ConvertAll((x) => x as ISomeObject);

        return conversion;
    

【讨论】:

【参考方案8】:

在我的项目中,这段代码总是作为一个默认的序列化器,它对指定的值进行序列化,就好像没有特殊的转换器一样:

serializer.Serialize(writer, value);

【讨论】:

序列化工作正常。问题是反序列化为声明为接口的属性。实现 CustomCreationConverter 并将其传递给 JsonConvert.DeserializeObject 似乎是答案。

以上是关于具有 IEnumerable<ISomeInterface> 类型属性的 NewtonSoft.Json 序列化和反序列化类的主要内容,如果未能解决你的问题,请参考以下文章

具有键“CategoryId”的 ViewData 项的类型为“System.Int32”,但必须为“IEnumerable<SelectListItem>”类型?

脚手架模型/视图:具有键“COLUMN”的 ViewData 项属于“System.String”类型,但必须属于“IEnumerable<SelectListItem>”类型

具有 IEnumerable<ISomeInterface> 类型属性的 NewtonSoft.Json 序列化和反序列化类

加载 HTML 时出现 MVC 错误。具有键“MY KEY”的 ViewData 项属于“System.String”类型,但必须属于“IEnumerable<SelectListItem>

通过反射动态修改 IEnumerable 属性

如何将两个 IEnumerable<T> 连接成一个新的 IEnumerable<T>?