2017-10-28 96 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在转换器列表的设置对象。

相关问题