2011-04-05 83 views
32

我需要將複雜的JSON blob反序列化爲標準.NET容器,以便在不知道JSON的代碼中使用。它期望事物處於標準的.NET類型中,特別是Dictionary [string,object]或List [object],其中「object」可以是原始的或遞歸的(Dictionary或List)。如何使用JSON.NET反序列化爲嵌套/遞歸Dictionary和List?

我不能使用靜態類型來映射結果,並且JObject/JToken不適合。理想情況下,會有某種方式(通過合同)來將原始的JSON轉換爲基本的.NET容器。

我已經搜索過任何方式來哄JSON.NET解串器創建這些簡單的類型時遇到「{}」或「[]」,但很少成功。

任何幫助表示讚賞!

+0

我試過Sys系統tem.Web.Script.Serialization.JavaScriptSerializer,它在這種情況下做我想做的,但我有其他原因想要堅持使用JSON.NET。 – dongryphon 2011-04-05 01:38:15

+0

更新:我現在所做的是在CreateJObject和CreateJToken方法中下載和修改Json.NET的源以創建我想要的類型。有8-10個單元測試要修復,但我可以忍受由此產生的妥協。 – dongryphon 2011-04-06 01:05:15

+0

對於它的價值,問題源於JsonSerializerInternalReader中HasDefinedType方法的用戶。 HasDefinedType檢查是在**之前**就如何創建目標對象諮詢合同,即使它確實嘗試了這一點,在知道合同是否爲「{}」或「[ ]「在場。我認爲有一些重構是爲了讓Json.NET將此決定外化,並允許用戶代碼在「對象」是已知的時候確定目標類型。 – dongryphon 2011-04-06 01:10:17

回答

1

你不能做我在問什麼。至少在我做了很多研究之後我還沒有看到。我不得不編輯Json.NET的源代碼。

+0

這個變化的任何機會都被推回到源代碼中或在任何地方可用? – Maslow 2011-07-06 01:14:32

+1

我有一個類似的問題,我的字典值有時包含數組(基本上是「[]」)。我第一次遇到這個問題,但很難理解爲什麼這個問題還沒有以通用的方式解決。對於似乎是一個非常基本的問題,這是一個死路一條。 任何人都想要插入並解釋什麼是JSON.NET反序列化(JSON.NET)的主要問題是? 其他人都可以控制他們的JSON並將其「更好」地結構化,或者我們在這裏缺少什麼? – PandaWood 2012-06-14 00:05:17

0

通過使用自定義的JsonConverter,您可以完全控制類型的序列化。文檔在http://james.newtonking.com/projects/json/help/html/T_Newtonsoft_Json_JsonConverter.htm

此外,根據this blog post您需要使用JArray作爲列表,JObject作爲詞典。

+0

感謝您的提示。我需要處理基於JSON的對象的反序列化:「{}」需要創建一個Dictionary [string,object],而[]需要創建一個List [object]或plain object []。我看不出如何將JsonCoverter連接到這個問題。即使在目標類型爲「對象」時使用合約之前,解串器中似乎也存在一些硬編碼邏輯。 – dongryphon 2011-04-05 15:07:12

+0

覆蓋合同解析器以連接自定義轉換器 – smartcaveman 2011-04-05 15:08:10

+0

謝謝,但我已經嘗試過。當類型落入解串器中的「!HasDefinedType」檢查時,不會使用這些契約。在JsonSerializerInternalReader.cs處取一個峯值並搜索HasDefinedType。你會看到一個對這個方法的調用,就在委託給合約的上方,如果這個類型是「object」,它會被這個檢查所捕獲。 – dongryphon 2011-04-08 01:38:33

1

我愛AutoMapper並認爲它解決了很多問題......像這樣的......

爲什麼不乾脆讓JSON.NET轉換成任何它想要的東西......和使用AutoMapper將其映射到您真正想要的對象中。

除非性能是最重要的,否則這個額外的步驟應該是值得的,因爲它可以降低複雜性並且可以使用所需的序列化程序。

+0

謝謝,我會研究一下。但是我仍然希望找到JSON.NET原生的東西,因爲它必須在面對「{}」到「對象」或「[]」時創建對象。我只是看不到如何控制它在這種情況下創建的對象的類型。 – dongryphon 2011-04-05 15:10:13

40

如果你只是想能夠處理任意JSON並將其轉換成普通.NET類型(原語,列表和字典)的嵌套結構,可以使用JSON.Net的LINQ-to-JSON API做一個通用的方法:

using System.Linq; 
using Newtonsoft.Json.Linq; 

public static class JsonHelper 
{ 
    public static object Deserialize(string json) 
    { 
     return ToObject(JToken.Parse(json)); 
    } 

    private static object ToObject(JToken token) 
    { 
     switch (token.Type) 
     { 
      case JTokenType.Object: 
       return token.Children<JProperty>() 
          .ToDictionary(prop => prop.Name, 
              prop => ToObject(prop.Value)); 

      case JTokenType.Array: 
       return token.Select(ToObject).ToList(); 

      default: 
       return ((JValue)token).Value; 
     } 
    } 
} 

您可以調用如下所示的方法。 obj將包含Dictionary<string, object>,List<object>或原語,具體取決於您開始的JSON。反序列化JSON字符串遞歸到字典和列表與JSON.NET

object obj = JsonHelper.Deserialize(jsonString); 
+0

希望你不要介意。我使用LINQ編輯代碼以使其更緊密。 – bradgonesurfing 2015-04-30 07:19:52

+0

@bradgonesurfing我喜歡它! – 2015-04-30 17:54:33

+0

你怎麼做token.Select line在vb.net? – NullVoxPopuli 2015-05-05 20:28:53

5

一種方法是創建一個由JSON.NET提供的JsonConverter抽象類派生的自定義JSON轉換器類。

它是在你的派生的JsonConverter你把實施應如何寫入和從json的對象。

您可以使用自定義的JsonConverter這樣的:

var o = JsonConvert.DeserializeObject<IDictionary<string, object>>(json, new DictionaryConverter()); 

這裏是一個自定義JsonConverter我已經成功在過去使用來實現相同的目標,你在你的問題概括:

public class DictionaryConverter : JsonConverter { 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { this.WriteValue(writer, value); } 

    private void WriteValue(JsonWriter writer, object value) { 
     var t = JToken.FromObject(value); 
     switch (t.Type) { 
      case JTokenType.Object: 
       this.WriteObject(writer, value); 
       break; 
      case JTokenType.Array: 
       this.WriteArray(writer, value); 
       break; 
      default: 
       writer.WriteValue(value); 
       break; 
     } 
    } 

    private void WriteObject(JsonWriter writer, object value) { 
     writer.WriteStartObject(); 
     var obj = value as IDictionary<string, object>; 
     foreach (var kvp in obj) { 
      writer.WritePropertyName(kvp.Key); 
      this.WriteValue(writer, kvp.Value); 
     } 
     writer.WriteEndObject(); 
    } 

    private void WriteArray(JsonWriter writer, object value) { 
     writer.WriteStartArray(); 
     var array = value as IEnumerable<object>; 
     foreach (var o in array) { 
      this.WriteValue(writer, o); 
     } 
     writer.WriteEndArray(); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { 
     return ReadValue(reader); 
    } 

    private object ReadValue(JsonReader reader) { 
     while (reader.TokenType == JsonToken.Comment) { 
      if (!reader.Read()) throw new JsonSerializationException("Unexpected Token when converting IDictionary<string, object>"); 
     } 

     switch (reader.TokenType) { 
      case JsonToken.StartObject: 
       return ReadObject(reader); 
      case JsonToken.StartArray: 
       return this.ReadArray(reader); 
      case JsonToken.Integer: 
      case JsonToken.Float: 
      case JsonToken.String: 
      case JsonToken.Boolean: 
      case JsonToken.Undefined: 
      case JsonToken.Null: 
      case JsonToken.Date: 
      case JsonToken.Bytes: 
       return reader.Value; 
      default: 
       throw new JsonSerializationException 
        (string.Format("Unexpected token when converting IDictionary<string, object>: {0}", reader.TokenType)); 
     } 
    } 

    private object ReadArray(JsonReader reader) { 
     IList<object> list = new List<object>(); 

     while (reader.Read()) { 
      switch (reader.TokenType) { 
       case JsonToken.Comment: 
        break; 
       default: 
        var v = ReadValue(reader); 

        list.Add(v); 
        break; 
       case JsonToken.EndArray: 
        return list; 
      } 
     } 

     throw new JsonSerializationException("Unexpected end when reading IDictionary<string, object>"); 
    } 

    private object ReadObject(JsonReader reader) { 
     var obj = new Dictionary<string, object>(); 

     while (reader.Read()) { 
      switch (reader.TokenType) { 
       case JsonToken.PropertyName: 
        var propertyName = reader.Value.ToString(); 

        if (!reader.Read()) { 
         throw new JsonSerializationException("Unexpected end when reading IDictionary<string, object>"); 
        } 

        var v = ReadValue(reader); 

        obj[propertyName] = v; 
        break; 
       case JsonToken.Comment: 
        break; 
       case JsonToken.EndObject: 
        return obj; 
      } 
     } 

     throw new JsonSerializationException("Unexpected end when reading IDictionary<string, object>"); 
    } 

    public override bool CanConvert(Type objectType) { return typeof(IDictionary<string, object>).IsAssignableFrom(objectType); } 
} 

這裏是f#等價的:

type IDictionaryConverter() = 
    inherit JsonConverter() 

    let rec writeValue (writer: JsonWriter) (value: obj) = 
      let t = JToken.FromObject(value) 
      match t.Type with 
      | JTokenType.Object -> writeObject writer value 
      | JTokenType.Array -> writeArray writer value 
      | _ -> writer.WriteValue value  

    and writeObject (writer: JsonWriter) (value: obj) = 
     writer.WriteStartObject() 
     let obj = value :?> IDictionary<string, obj> 
     for kvp in obj do 
      writer.WritePropertyName kvp.Key 
      writeValue writer kvp.Value 
     writer.WriteEndObject()  

    and writeArray (writer: JsonWriter) (value: obj) = 
     writer.WriteStartArray() 
     let array = value :?> IEnumerable<obj> 
     for o in array do 
      writeValue writer o 
     writer.WriteEndArray() 

    let rec readValue (reader: JsonReader) = 
     while reader.TokenType = JsonToken.Comment do 
      if reader.Read() |> not then raise (JsonSerializationException("Unexpected token when reading object")) 

     match reader.TokenType with 
     | JsonToken.Integer 
     | JsonToken.Float 
     | JsonToken.String 
     | JsonToken.Boolean 
     | JsonToken.Undefined 
     | JsonToken.Null 
     | JsonToken.Date 
     | JsonToken.Bytes -> reader.Value 
     | JsonToken.StartObject -> readObject reader Map.empty 
     | JsonToken.StartArray -> readArray reader [] 
     | _ -> raise (JsonSerializationException(sprintf "Unexpected token when reading object: %O" reader.TokenType)) 


    and readObject (reader: JsonReader) (obj: Map<string, obj>) = 
     match reader.Read() with 
     | false -> raise (JsonSerializationException("Unexpected end when reading object")) 
     | _ -> reader.TokenType |> function 
      | JsonToken.Comment -> readObject reader obj 
      | JsonToken.PropertyName -> 
       let propertyName = reader.Value.ToString() 
       if reader.Read() |> not then raise (JsonSerializationException("Unexpected end when reading object")) 
       let value = readValue reader 
       readObject reader (obj.Add(propertyName, value)) 
      | JsonToken.EndObject -> box obj 
      | _ -> raise (JsonSerializationException(sprintf "Unexpected token when reading object: %O" reader.TokenType)) 

    and readArray (reader: JsonReader) (collection: obj list) = 
     match reader.Read() with 
     | false -> raise (JsonSerializationException("Unexpected end when reading array")) 
     | _ -> reader.TokenType |> function 
      | JsonToken.Comment -> readArray reader collection 
      | JsonToken.EndArray -> box collection 
      | _ -> collection @ [readValue reader] |> readArray reader 

    override __.CanConvert t = (typeof<IDictionary<string, obj>>).IsAssignableFrom t 
    override __.WriteJson (writer:JsonWriter, value: obj, _:JsonSerializer) = writeValue writer value 
    override __.ReadJson (reader:JsonReader, _: Type, _:obj, _:JsonSerializer) = readValue reader 
+0

謝謝你。它解決了一半的挑戰。後半部分是不使用C#對象,而是使用MatLab MWArray派生對象。 在這裏,我可以訪問每個值,它的屬性名稱,所以它看起來像從這裏直道。 – Mariusz 2017-01-12 15:33:21