2017-10-28 195 views
2

我想從Web響應中反序列化JSON。這是一個典型的迴應:如何使用自定義的JsonConverter來僅對Json.NET中的子對象進行反序列化?

{ 
    "response": { 
     "garbage": 0, 
     "details": [ 
      { 
       "id": "123456789" 
      } 
     ] 
    } 
} 

但是,這種格式是不可取的。理想情況下,他們的迴應是剛剛

{ 
    "id": "123456789" 
} 

,以便它可以deserialised爲對象像

public class Details { 
    [JsonProperty("id")] 
    public ulong Id { get; set; } 
} 

因爲我沒有控制服務器(這是一個公共API),我的目標是修改反序列化過程來實現我想要的格式。


我試圖使用自定義JsonConverter來實現這一點。這個想法是跳過令牌,直到找到想要的反序列化起始點Details。但是,我不確定應該在反序列化過程中使用它。

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Text; 
using Newtonsoft.Json; 

namespace ConsoleApp2 { 
    class Program { 
     static void Main(string[] args) { 
      // Simulating a stream from WebResponse. 
      const string json = "{"response":{"garbage":0,"details":[{"id":"123456789"}]}}"; 
      byte[] bytes = System.Text.Encoding.UTF8.GetBytes(json); 
      Stream stream = new MemoryStream(bytes); 

      Details details = Deserialise<Details>(stream); 

      Console.WriteLine($"ID: {details.Id}"); 
      Console.Read(); 
     } 

     public static T Deserialise<T>(Stream stream) where T : class { 
      using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { 
       JsonSerializerSettings settings = new JsonSerializerSettings { 
        MissingMemberHandling = MissingMemberHandling.Ignore, 
        DateParseHandling = DateParseHandling.None 
       }; 

       settings.Converters.Add(new DetailConverter()); 
       return JsonSerializer.Create(settings).Deserialize(reader, typeof(T)) as T; 
      } 
     } 
    } 

    public class Details { 
     [JsonProperty("id")] 
     public ulong Id { get; set; } 
    } 

    public class DetailConverter : JsonConverter { 
     public override void WriteJson(JsonWriter writer, 
             object value, 
             JsonSerializer serializer) { 
      throw new NotImplementedException(); 
     } 

     public override object ReadJson(JsonReader reader, 
             Type objectType, 
             object existingValue, 
             JsonSerializer serializer) { 
      if (reader.Depth == 0) { 
       while (!(reader.Path.Equals("response.details[0]", StringComparison.OrdinalIgnoreCase) && reader.TokenType == JsonToken.StartObject)) { 
        reader.Read(); 
       } 

       try { 
        return serializer.Deserialize(reader, objectType); 
       } finally { 
        reader.Read(); // EndArray - details 
        reader.Read(); // EndObject - response 
        reader.Read(); // EndObject - root 
       } 
      } 

      return serializer.Deserialize(reader, objectType); 
     } 

     public override bool CanWrite => false; 
     public override bool CanConvert(Type objectType) => true; 
    } 
} 

就目前情況來看,現在,該堆棧溢出,因爲DetailConverter.ReadJson()正在對同一對象重複使用,它從來沒有得到deserialised。我認爲這是因爲我通過JsonSerializerSettingsDetailConverter設置爲「全局」轉換器。我認爲問題在於何時以及如何使用我的轉換器,而不是在其內部工作。


我收到了一個類似的DetailConverter爲以下結構工作。但是,從details刪除陣列時,由於嵌套和未使用的屬性,它仍然不受歡迎。

public class Root { 
    [JsonProperty("response")] 
    public Response Response { get; set; } 
} 

public class Response { 
    [JsonProperty("details")] 
    [JsonConverter(typeof(DetailConverter))] 
    public Details Details { get; set; } 

    [JsonProperty("garbage")] 
    public uint Garbage { get; set; } 
} 

public class Details { 
    [JsonProperty("id")] 
    public ulong Id { get; set; } 
} 

我認爲將轉換器擴展到整個JSON而不僅僅是一個屬性是很簡單的。我哪裏做錯了?

+0

你真的應該使用默認的'JsonConverter'的第二種方法。此外,如果您不使用它,則不必聲明「垃圾」 - 只有直接的父母纔會這樣做。 – aaron

+0

有了很多嵌套的更大的JSON文件並不像代碼生成器那樣煩人。然而,創建一堆虛擬父類似乎並不優雅。 – Mark

+0

@aaron有2天的等待期。 – Mark

回答

0

解決的方法是先清除JsonSerializer的轉換器列表,然後從我的JsonConverter調用JsonSerializer.Deserialize

我從here的想法,其中用戶介紹如何歸零了JsonContract將恢復的JsonConverter到默認JsonConverter。這樣可以避免重複調用我自定義的JsonConverter來解決我想要反序列化的子對象的問題。

public override object ReadJson(JsonReader reader, 
           Type objectType, 
           object existingValue, 
           JsonSerializer serializer) { 
    while (reader.Depth != 3) { 
     reader.Read(); 
    } 

    serializer.Converters.Clear(); 

    return serializer.Deserialize(reader, objectType); 
} 

該代碼也被簡化,除去try - finally塊。沒有必要讀取結束標記,因爲永遠不會嘗試對它們進行反序列化。也不需要檢查深度,因爲自定義JsonConverter只能使用一次。

1

Linq to JSON會爲你工作嗎?

using System; 
using Newtonsoft.Json.Linq; 

public class Program 
{ 
    public static void Main() 
    { 
     var json = "{'response':{'garbage':0,'details':[{'id':'123456789'}]}}"; 
     var obj = JObject.Parse(json); 
     var details = obj["response"]["details"]; 

     Console.WriteLine(details); 
    } 
} 
+0

我不知道,我不熟悉它。我想繼續使用流而不是轉換爲字符串。我也用了一個簡單的例子。我使用更多轉換器來轉換字典數組到其值的數組並將unix時間戳轉換爲日期對象。 – Mark

+0

@Mark所以你想解決一個你無法解決的問題,但又不想改變你的工作方式?有趣。 – Gusdor

+0

但是或許足夠公平,一些流對於某些場景更有用。 –

0

它看起來像你想從細節陣列中的單個值,所以我的答案只是需要第一。這樣的轉換器會工作嗎?它採用JObject並從細節數組中選出一個值,然後將其轉換爲Details類。

public class DetailsConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return typeof(Details) == objectType; 
    } 

    public override bool CanWrite 
    { 
     get 
     { 
      return false; 
     } 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     var obj = JObject.Load(reader); 
     var value = obj["response"]?["details"]?.FirstOrDefault(); 
     if (value == null) { return null; } 
     return value.ToObject<Details>(); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 
} 

當您使用串行,你可以簡單地調用它是這樣的:

var details = JsonConvert.DeserializeObject<Details>(json, settings);

請注意,您需要創建JsonSerializerSettings settings,並且包括DetailsConverter在轉換器列表的設置對象。

相關問題