如何利用自定义JsonConverter在Json.NET?

时间:2017-10-28 07:50:56

标签: c# json json.net deserialization json-deserialization

我想从Web响应中反序列化JSON。这是一个典型的回应:

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

但是,这种格式是不合需要的。理想情况下,响应只是

{
    "id": "123456789"
}

以便可以将其反序列化为像

这样的对象
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()被重复地用在同一个对象上而且它永远不会被反序列化。我认为这是因为我将DetailConverter设置为&#34;全球&#34;通过JsonSerializerSettings转换。我认为问题在于我的转换器何时以及如何使用而不是在其内部工作中。

我已经获得了类似的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而不仅仅是一个属性是直截了当的。我哪里出错了?

3 个答案:

答案 0 :(得分: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);
    }
}

答案 1 :(得分:0)

看起来你想要来自details数组的单个值,所以我的答案就是第一个。这样的转换器会起作用吗?它接受JObject并从details数组中选取一个值,然后将其转换为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。

答案 2 :(得分:0)

解决方案是在从JsonSerializer调用JsonSerializer.Deserialize之前清除JsonConverter的转换器列表。

我从here得到了这个想法,其中一位用户描述了如何将JsonConverter的{​​{1}}归零为默认JsonContract。这避免了为我希望反序列化的子对象重复调用我的自定义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将仅使用一次。