76

我想通过 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>集合呢?

4

9 回答 9

91

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

实际上,我发现我不需要任何自定义活页夹,只要我告诉它我正在做的事情,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 设置

于 2013-10-31T15:44:14.220 回答
43

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

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();
    }
}
于 2013-04-08T14:19:52.000 回答
27

我也对 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 枚举文档

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

于 2015-05-23T00:30:19.983 回答
7

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

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

于 2013-04-08T14:00:11.057 回答
7

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

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

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

于 2016-05-06T01:44:55.880 回答
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);
于 2018-03-11T19:04:42.627 回答
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; }
}
于 2017-02-16T06:18:00.567 回答
3

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

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

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

于 2016-02-24T21:37:56.973 回答
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;
    }

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

于 2018-10-12T17:34:10.883 回答