【问题标题】:NewtonSoft.Json Serialize and Deserialize class with property of type IEnumerable<ISomeInterface>具有 IEnumerable<ISomeInterface> 类型属性的 NewtonSoft.Json 序列化和反序列化类
【发布时间】: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 方法以使序列化再次工作。

有人可以帮忙吗?

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

【问题讨论】:

标签: c# json serialization json.net deserialization


【解决方案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 类可能有任何类型的基类型字段,并且会被正确序列化。

      【讨论】:

      【解决方案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 对象初始化器,这对您很重要。

        【讨论】:

        【解决方案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 似乎是答案。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-06-07
            • 2015-05-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多