2013-04-08 58 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; } 
}