2013-04-08 73 views
44

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

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

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

所以,我怎么能反序列化List集合?

感谢

+0

你已经试过了什么?你甚至读过JSON.NET的文档吗?我非常肯定,序列化和反序列化是这种文档将要覆盖的第一件事情之一。 – Clint 2013-04-08 13:39:56

+0

是序列化正在工作,但当我尝试反序列化时出现错误:Newtonsoft.Json.JsonSerializationException:无法创建ITestInterface类型的实例。 Type是一个接口或抽象类,不能实例化。 – user1130329 2013-04-08 13:47:10

+2

那么也许你应该在你的问题中引导你?而不是问这样的开放式“我怎么能?” “它不起作用”的问题,你真的需要提供所有的信息,它有什么错误?它在哪里发生?你到目前为止尝试解决这个问题?请使用这些内容编辑您的问题,以便社群能够更好地帮助您,而不是标记您的问题。 – Clint 2013-04-08 13:48:47

回答

27

娄全工作例如你想要做什么:

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

这只是完美的工作。非常感谢你! – user1130329 2013-04-08 16:34:25

+0

您可以在活页夹内使用FullyQualifiedName而不是名称,并且您不必通过TypeFormat – 2017-03-09 16:37:27

+0

真的对我的问题也有帮助!非常感谢 – 2018-01-13 10:56:11

8

使用默认设置,你不能。 JSON.NET无法知道如何反序列化数组。但是,您可以指定将哪种类型转换器用于您的接口类型。要了解如何做到这一点,看到这个页面:http://blog.greatrexpectations.com/2012/08/30/deserializing-interface-properties-using-json-net/

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

+0

这里提到的博客有更详细的解释。 upvote for erik – 2014-04-03 07:20:06

71

我在尝试自己做这件事时发现了这个问题。在实施Garath's answer后,我感到似乎有多简单。如果我只是实现了一个方法,它已经被传入了我想要实例化的确切类型(作为一个字符串),为什么库不能自动绑定它?

我居然发现,我并不需要任何定制粘合剂,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 
}); 

相关文章:Serialization Settings for Json.NETTypeNameHandling setting

+7

这确实有效,但人们应该明白这可能会让你的json大片时间膨胀。对我而言,它将输出文件大小增加了10倍以上。 – 2015-02-16 04:06:15

+2

请注意,在生成的JSON中放入具体类型名称可能会泄漏实现细节,并且如果JSON在您自己的代码之外的任何位置使用,则绝对不会干净。另外,如果您要反序列化的JSON来自外部来源,期望它包含您的类型名称是不合理的。 – 2015-08-29 22:09:18

+1

使用此解决方案,应该清理传入类型以避免潜在的安全隐患。有关详细信息,请参见[Newtonsoft Json中的TypeNameHandling注意事项](https://stackoverflow.com/q/39565954/3744182)。 – dbc 2018-02-02 21:26:14

13

我也由简单位于Garath的惊讶,也来到Json库可以自动完成的结论。但我也认为它比Ben Jenkinson的答案更简单(尽管我可以看到它已由json库自己的开发者修改过)。从我testings,所有你需要做的是设置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 }); 
+1

这是原来的帖子几年后,但这是一个最新的和工作的方式。 您付出的代价是类型名称在JSON中的每个对象上都以“$ type”属性的形式输出,但在很多情况下这是很好的。 – Grubl3r 2016-02-10 12:41:45

+0

这是正确的解决方案,也适用于更复杂的数据结构。花了我几天的时间找到它,虽然...你可以添加到设置TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle。简单'尽可能缩短输出类型 – metafa 2017-12-12 10:12:08

+0

使用此解决方案时,还应该清理输入类型以避免潜在的安全隐患。有关详细信息,请参见[Newtonsoft Json中的TypeNameHandling注意事项](https://stackoverflow.com/q/39565954/3744182)。 – dbc 2018-02-02 21:26:57

3

近重复Inrego的答案,但它是值得进一步的解释:如果您使用TypeNameHandling.Auto那么只有它

包括类型/集名称时需要到(即接口和基/派生类)它。所以你的JSON更清晰,更小,更具体。

这是不是在XML/SOAP它的主要卖点的那一个?

4

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

TLDR:而不是配置Json.NET嵌入在序列化的JSON中键入名称,可以使用JSON转换器来找出要使用任何您喜欢的自定义逻辑反序列化的类。

这样做的好处是你可以重构你的类型而不用担心反序列化的破坏。

2

我想反序列化未被我的应用程序序列化的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; } 
}