简体   繁体   English

如何使用自定义 JsonConverter 仅反序列化 Json.NET 中的子对象?

[英]How to make use of a custom JsonConverter to deserialise only a child object in Json.NET?

I want to deserialise JSON from a web response.我想从 Web 响应反序列化 JSON。 Here's a typical response:这是一个典型的响应:

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

However, this format is undesirable.然而,这种格式是不可取的。 Ideally, the response would be just理想情况下,响应只是

{
    "id": "123456789"
}

so that it could be deserialised into an object like以便它可以反序列化为一个对象

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

Since I have no control of the server (it's a public API), I aim to modify the deserialisation process to achieve my desired format.由于我无法控制服务器(它是一个公共 API),我的目标是修改反序列化过程以实现我想要的格式。


I've tried to make use of a custom JsonConverter to accomplish this.我尝试使用自定义JsonConverter来完成此操作。 The idea is to skip tokens until I find the desired starting point for deserialisation into Details .这个想法是跳过令牌,直到找到反序列化为Details所需的起点。 However, I'm not sure where it should be used in the deserialisation process.但是,我不确定在反序列化过程中应该在哪里使用它。

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;
    }
}

As things stand right now, the stack overflows because DetailConverter.ReadJson() is being used on the same object repeatedly and it never gets deserialised.就目前的情况而言,堆栈溢出是因为DetailConverter.ReadJson()被反复用于同一个对象并且它永远不会被反序列化。 I think it's because I've set DetailConverter as a "global" converter through JsonSerializerSettings .我认为这是因为我通过JsonSerializerSettingsDetailConverter设置为“全局”转换器。 I think the issues lies in when and how my converter is being used rather than in its inner workings.我认为问题在于何时以及如何使用我的转换器,而不是其内部工作原理。


I've gotten a similar DetailConverter to work for the following structure.我已经得到了一个类似的DetailConverter来处理以下结构。 However, while the array from details is removed, it's still undesirable because of nesting and unused properties.然而,虽然从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; }
}

I thought it'd be straightforward to scale up the converter to the entire JSON rather than just one property.我认为将转换器扩展到整个 JSON 而不仅仅是一个属性会很简单。 Where did I go wrong?我哪里做错了?

Will Linq to JSON work for you? 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);
    }
}

It looks like you want a single value from the details array, so my answer just takes the first.看起来您想要来自 details 数组的单个值,所以我的回答只取第一个。 Would a converter like this work?像这样的转换器可以工作吗? It takes the JObject and picks out a single value from the details array, and then converts that into the Details class.它获取 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();
    }
}

When you use the serializer, you can simply call it like this:当您使用序列化程序时,您可以简单地像这样调用它:

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

Note that you need to create JsonSerializerSettings settings , and include DetailsConverter in the Converters list of the settings object.请注意,您需要创建JsonSerializerSettings settings ,并在设置对象的转换器列表中包含JsonSerializerSettings settings

The solution is to clear the list of converters of the JsonSerializer before calling JsonSerializer.Deserialize from my JsonConverter .解决方案是在从我的JsonConverter调用JsonSerializer.Deserialize之前清除JsonSerializer的转换器列表。

I got the idea from here , in which a user describes how nulling out the JsonConverter of a JsonContract reverts to the default JsonConverter .我从这里得到了一个想法,其中用户描述了如何将JsonConverterJsonContract恢复到默认的JsonConverter This avoids the problem of repeatedly calling my custom JsonConverter for the child object I wish to deserialise.这避免了为我希望反序列化的子对象重复调用我的自定义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);
}

The code has also been simplified, removing the try - finally block.代码也得到了简化,删除了try - finally块。 It's not necessary to read the ending tokens because there will never be an attempt to deserialise them anyway.没有必要读取结束标记,因为无论如何都不会尝试反序列化它们。 There is also no need to check for the depth because the custom JsonConverter will only be used once.也不需要检查深度,因为自定义JsonConverter只会使用一次。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM