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


74

我正在尝试移动一些代码以使用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。请参阅如何在自定义JsonConverter中使用默认序列化
dbc

@AndyDBell-您可能要考虑接受答案之一作为该线程中的接受答案。已经有一些很好的答案。您可以使用投票按钮下方的复选标记将最有帮助的答案标记为已接受答案。
RBT

Answers:


70

您无需使用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());

文档:使用CustomCreationConverter反序列化


19

这非常简单,并且由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对象初始化程序,这对您很重要。


14

我通过使用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

很好的解决方案,谢谢!我以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());
    }
}

2

在我的项目中,这段代码始终充当默认的序列化程序,该序列化程序将指定的值序列化为好像没有特殊转换器的情况:

serializer.Serialize(writer, value);

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

1

我得到这个工作:

显式转换

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

0

具有:

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

0

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

[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
    }
  ]
}

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.