反序列化接口实例的集合?


70

我想通过json.net将此代码序列化:

public interface ITestInterface
{
    string Guid {get;set;}
}

public class TestClassThatImplementsTestInterface1
{
    public string Guid { get;set; }
}

public class TestClassThatImplementsTestInterface2
{
    public string Guid { get;set; }
}


public class ClassToSerializeViaJson
{
    public ClassToSerializeViaJson()
    {             
         this.CollectionToSerialize = new List<ITestInterface>();
         this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
         this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
    }
    List<ITestInterface> CollectionToSerialize { get;set; }
}

我想使用json.net序列化/反序列化ClassToSerializeViaJson。序列化正在工作,但是反序列化给了我这个错误:

Newtonsoft.Json.JsonSerializationException:无法创建ITestInterface类型的实例。类型是接口或抽象类,无法实例化。

那么我如何反序列化List<ITestInterface>集合?


您已经尝试了什么?您甚至还阅读过JSON.NET的文档吗?我敢肯定,序列化和反序列化是此类文档首先要介绍的内容之一。
克林特(Clint)2013年

1
是序列化正在工作,但是尝试反序列化时出现错误:Newtonsoft.Json.JsonSerializationException:无法创建ITestInterface类型的实例。类型是接口或抽象类,无法实例化。
user1130329 2013年

2
那么也许您应该在这个问题上做到这一点?而不是问这种开放式的“我怎么能?” “它不起作用”的问题,您真的需要提供所有信息,这是什么错误?发生在哪里?到目前为止,您已尝试解决什么问题?请使用这些内容来编辑您的问题,以便社区可以更好地为您提供帮助,而不是标记您的问题。
克林特

1
尼古拉斯·韦斯特比(Nicholas Westby)在一篇很棒的文章中
A. Morel

Answers:


41

贝娄完整的工作示例,其中包含您要执行的操作:

public interface ITestInterface
{
    string Guid { get; set; }
}

public class TestClassThatImplementsTestInterface1 : ITestInterface
{
    public string Guid { get; set; }
    public string Something1 { get; set; }
}

public class TestClassThatImplementsTestInterface2 : ITestInterface
{
    public string Guid { get; set; }
    public string Something2 { get; set; }
}

public class ClassToSerializeViaJson
{
    public ClassToSerializeViaJson()
    {
        this.CollectionToSerialize = new List<ITestInterface>();
    }
    public List<ITestInterface> CollectionToSerialize { get; set; }
}

public class TypeNameSerializationBinder : SerializationBinder
{
    public string TypeFormat { get; private set; }

    public TypeNameSerializationBinder(string typeFormat)
    {
        TypeFormat = typeFormat;
    }

    public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = null;
        typeName = serializedType.Name;
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        var resolvedTypeName = string.Format(TypeFormat, typeName);
        return Type.GetType(resolvedTypeName, true);
    }
}

class Program
{
    static void Main()
    {
        var binder = new TypeNameSerializationBinder("ConsoleApplication.{0}, ConsoleApplication");
        var toserialize = new ClassToSerializeViaJson();

        toserialize.CollectionToSerialize.Add(
            new TestClassThatImplementsTestInterface1()
            {
                Guid = Guid.NewGuid().ToString(), Something1 = "Some1"
            });
        toserialize.CollectionToSerialize.Add(
            new TestClassThatImplementsTestInterface2()
            {
                Guid = Guid.NewGuid().ToString(), Something2 = "Some2"
            });

        string json = JsonConvert.SerializeObject(toserialize, Formatting.Indented, 
            new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = binder
            });
        var obj = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(json, 
            new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = binder 
            });

        Console.ReadLine();
    }
}

1
您可以在活页夹中使用FullyQualifiedName而不是name,而不必通过TypeFormat
Slava Shpitalny

3
这对我来说很好。自从不久前回答以来,已有一些更新。看来他们现在正在使用接口ISerializationBinder而不是覆盖SerializationBinder。同样在BindToName我中,我使用serializedType.AssemblyQualifiedName了Name而不是Name,并且将assemblyName和都合格了typeNameBindToType所以现在不需要构造函数了。然后BindToTypevar resolvedTypeName = string.Format("{0}, {1}", typeName,assemblyName); and everything should work without providing the namespace & assembly in the constructor
Crob

89

我在尝试自己做时发现了这个问题。在实现了Piotr Stapp(Garath)的答案之后,我被它看起来如此简单而震惊。如果我只是实现一个已经传递了要实例化的确切Type(作为字符串)的方法,为什么库不自动绑定它?

我实际上发现我不需要任何自定义活页夹,只要告诉我这就是我正在做的事情,Json.Net就能完全满足我的需要。

序列化时:

string serializedJson = JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
});

反序列化时:

var deserializedObject = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(serializedJson, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects
});

相关文档:Json.NET的序列化设置TypeNameHandling设置


15
这确实有效,但是人们应该理解,这可能会使您的json膨胀得很大。对我来说,它使输出文件的大小增加了10倍以上。

11
请注意,将具体类型名称放入结果JSON中可能会被认为泄漏了实现细节,并且如果在您自己的代码之外的任何地方使用JSON,则绝对不干净。另外,如果您要反序列化的JSON来自外部来源,则期望它包含您的类型名称是不合理的。
JacekGorgoń15年

4
使用此解决方案,应该清理传入的类型,以避免潜在的安全隐患。有关详细信息,请参见Newtonsoft Json中的TypeNameHandling警告
dbc

Ingero下面的答案通过使用TypeNameHandling.Auto而不是TypeNameHandling.Objects来最大限度地减少$ types的污染
Michael Freidgeim

我同意@JacekGorgoń;我从ASP.NET核心控制器自动获取JSON。
rory.ap

26

我也对Garath的简单性感到惊讶,并得出结论说Json库可以自动完成它。但是我也认为它比Ben Jenkinson的答案更简单(即使我可以看到它已经由json库的开发人员自己修改了)。从我的测试中,您需要做的就是将TypeNameHandling设置为Auto,如下所示:

var objectToSerialize = new List<IFoo>();
// TODO: Add objects to list
var jsonString = JsonConvert.SerializeObject(objectToSerialize,
       new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
var deserializedObject = JsonConvert.DeserializeObject<List<IFoo>>(jsonString, 
       new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });

来自TypeNameHandling枚举文档

自动:当要序列化的对象的类型与其声明的类型不同时,请包括.NET类型名称。请注意,默认情况下这不包括根序列化的对象。


2
距原始帖子已过了几年,但这是一种最新且可行的方法。您要付出的代价是类型名称在JSON中的每个对象上都作为“ $ type”属性输出,但这在许多情况下都可以。
Grubl3r

1
对于更复杂的数据结构,这也是正确的解决方案。花了我几天的时间才找到它...您可以添加到设置中TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple以尽可能缩短类型输出
metafa

1
同样,使用这种解决方案,应该对传入的类型进行清理,以避免潜在的安全隐患。有关详细信息,请参见Newtonsoft Json中的TypeNameHandling警告
dbc

应该注意的是,这仅在创建JSON时可以控制您的情况下有效。如果是从其他任何地方创建的,则该反序列化将无法进行。
小怪兽


7

这是一个古老的问题,但是我想我应该添加一个更深入的答案(以我写的文章的形式):http : //skrift.io/articles/archive/bulletproof-interface-deserialization-in-jsonnet /

TLDR:您可以使用JSON转换器来确定要反序列化哪个类,而不是使用Json.NET在串行化的JSON中嵌入类型名称,而是使用您喜欢的任何自定义逻辑。

这样的好处是您可以重构类型而不必担心反序列化的中断。


5

可以使用JSON.NET和JsonSubTypes属性来完成:

[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test1), "Something1")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test2), "Something2")]
public interface ITestInterface
{
    string Guid { get; set; }
}

public class Test1 : ITestInterface
{
    public string Guid { get; set; }
    public string Something1 { get; set; }
}

public class Test2 : ITestInterface
{
    public string Guid { get; set; }
    public string Something2 { get; set; }
}

并简单地:

var fromCode = new List<ITestInterface>();
// TODO: Add objects to list
var json = JsonConvert.SerializeObject(fromCode);
var fromJson = JsonConvert.DeserializeObject<List<ITestInterface>>(json);

4

我想反序列化未由我的应用程序序列化的JSON,因此我需要手动指定具体的实现。我扩大了尼古拉斯的答案。

可以说我们有

public class Person
{
    public ILocation Location { get;set; }
}

和的具体实例

public class Location: ILocation
{
    public string Address1 { get; set; }
    // etc
}

在这个课程中添加

public class ConfigConverter<I, T> : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(I);
    }
    public override void WriteJson(JsonWriter writer,
        object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var deserialized = (T)Activator.CreateInstance(typeof(T));
        serializer.Populate(jsonObject.CreateReader(), deserialized);
        return deserialized;
    }
}

然后使用JsonConverter属性定义接口

public class Person
{
    [JsonConverter(typeof(ConfigConverter<ILocation, Location>))]
    public ILocation Location { get;set; }
}

一些实现使用serializer.Populate其他使用jsonObject.ToObject。有区别吗?
xr280xr

3

Inrego的答案几乎重复,但是值得进一步解释:

如果使用,TypeNameHandling.Auto则仅在需要时包含类型/程序集名称(即接口和基类/派生类)。因此,您的JSON更干净,更小,更具体。

那不是XML / SOAP的主要卖点之一吗?


2

尽可能避免使用TypeNameHandling.Auto,尤其是用户可控制的值。

您将需要为集合类型编写自己的反序列化器。

我没有重复其他已经发布样板转换器代码的人(特别是Nicholas Westby,其博客文章非常有用并且在上面链接),而是包括了一些反序列化接口集合的相关更改(我具有枚举接口属性以区分实现者):

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        Collection<T> result = new Collection<T>();
        var array = JArray.Load(reader);
        foreach (JObject jsonObject in array)
        { 
            var rule = default(T);
            var value = jsonObject.Value<string>("MyDistinguisher");
            MyEnum distinguisher;
            Enum.TryParse(value, out distinguisher);
            switch (distinguisher)
            {
                case MyEnum.Value1:
                    rule = serializer.Deserialize<Type1>(jsonObject.CreateReader());
                    break;
                case MyEnum.Value2:
                    rule = serializer.Deserialize<Type2>(jsonObject.CreateReader());
                    break;
                default:
                    rule = serializer.Deserialize<Type3>(jsonObject.CreateReader());
                    break;
            }
            result.Add(rule);
        }
        return result;
    }

我希望这对下一个寻找接口集合解串器的人有所帮助。


如何使用system.text.json实现?
安东·埃里克森
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.