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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15880574/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 18:17:04  来源:igfitidea点击:

Deserialize collection of interface-instances?

c#json.net

提问by user1130329

I would like to serialize this code via json.net:

我想通过 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; }
}

I want to serialize/deserialize ClassToSerializeViaJson with json.net. Serialization is working, but deserialization gives me this error:

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

Newtonsoft.Json.JsonSerializationException: Could not create an instance of type ITestInterface. Type is an interface or abstract class and cannot be instantiated.

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

So how can I deserialize the List<ITestInterface>collection?

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

采纳答案by Piotr Stapp

Bellow full working example with what you want to do:

波纹管完整的工作示例与您想要做什么:

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

回答by Erik Schierboom

Using the default settings, you cannot. JSON.NET has no way of knowing how to deserialize an array. However, you can specify which type converter to use for your interface type. To see how to do this, see this page: http://blog.greatrexpectations.com/2012/08/30/deserializing-interface-properties-using-json-net/

使用默认设置,您不能。JSON.NET 无法知道如何反序列化数组。但是,您可以指定要为您的接口类型使用的类型转换器。要了解如何执行此操作,请参阅此页面:http: //blog.greatrexpectations.com/2012/08/30/deserializing-interface-properties-using-json-net/

You can also find information about this problem at this SO question: Casting interfaces for deserialization in JSON.NET

您还可以在此 SO 问题中找到有关此问题的信息:Casting interfaces for deserialization in JSON.NET

回答by Ben Jenkinson

I found this question while trying to do this myself. After I implemented Piotr Stapp's(Garath's) answer, I was struck by how simple it seemed. If I was merely implementing a method that was already being passed the exact Type (as a string) that I wanted to instantiate, why wasn't the library binding it automatically?

我在自己尝试这样做时发现了这个问题。在我实施了Piotr Stapp's(Garath's) answer 后,我被它看起来的简单所震惊。如果我只是实现一个已经传递了我想要实例化的确切类型(作为字符串)的方法,为什么库没有自动绑定它?

I actually found that I didn't need any custom binders, Json.Net was able to do exactly what I needed, provided I told it that was what I was doing.

我实际上发现我不需要任何自定义绑定器,Json.Net 能够完全满足我的需要,只要我告诉它这就是我正在做的。

When serializing:

序列化时:

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

When de-serializing:

反序列化时:

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

Relevant documentation: Serialization Settings for Json.NETand TypeNameHandling setting

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

回答by Inrego

I was also surprised by the simplicity in Garath's, and also came to the conclusion that the Json library can do it automatically. But I also figured that it's even simpler than Ben Jenkinson's answer (even though I can see it has been modified by the developer of the json library himself). From my testings, all you need to do is set TypeNameHandling to Auto, like this:

我也对 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 });

From TypeNameHandling Enumeration documentation

来自TypeNameHandling 枚举文档

Auto: Include the .NET type name when the type of the object being serialized is not the same as its declared type. Note that this doesn't include the root serialized object by default.

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

回答by sliderhouserules

Near-duplicate of Inrego's answer, but it's worthy of further explanation:

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

If you use TypeNameHandling.Autothen it only includes the type/assembly name when it needsto (i.e. interfaces and base/derived classes). So your JSON is cleaner, smaller, more specific.

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

Which isn't that one of the main selling points of it over XML/SOAP?

哪个不是它在 XML/SOAP 上的主要卖点之一?

回答by Nicholas Westby

This is an old question, but thought I'd add a more in-depth answer (in the form of an article I wrote): http://skrift.io/articles/archive/bulletproof-interface-deserialization-in-jsonnet/

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

TLDR:Rather than configure Json.NET to embed type names in the serialized JSON, you can use a JSON converter to figure out which class to deserialize to using whatever custom logic you like.

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

This has the advantage that you can refactor your types without worrying about deserialization breaking.

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

回答by Adam Pedley

I wanted to deserialize JSON that wasn't serialized by my application, hence I needed to specify the concrete implementation manually. I have expanded on Nicholas's answer.

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

Lets say we have

可以说我们有

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

and the concrete instance of

和具体实例

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

Add in this class

在这个类中添加

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

Then define your interfaces with the JsonConverter attribute

然后使用 JsonConverter 属性定义您的接口

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

回答by manuc66

It can be done with JSON.NET and JsonSubTypesattributes:

可以使用 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; }
}

and simply:

简单地说:

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

回答by ConsideredHarmful

Avoid TypeNameHandling.Auto when possible, particularly with user-controllable values.

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

You will need to write your own deserializer for the collection type.

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

Rather than repeat others who have already posted boilerplate converter code (particularly Nicholas Westby, whose blog post was quite useful and is linked above), I have included the relevant changes for deserializing a collection of interfaces (I had an enum interface property to distinguish implementors):

我没有重复已经发布样板转换器代码的其他人(特别是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;
    }

I hope this is helpful to the next person looking for an interface collection deserializer.

我希望这对寻找接口集合反序列化器的下一个人有所帮助。