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

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

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

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

但是,这种格式是不可取的。理想情况下,响应只是

{
    "id": "123456789"
}

这样它就可以被反序列化为一个对象,比如

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

由于我无法控制服务器(它是 public 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() 被重复用于同一个对象并且它永远不会被反序列化。我认为这是因为我通过 JsonSerializerSettingsDetailConverter 设置为 "global" 转换器。我认为问题在于何时以及如何使用我的转换器,而不是其内部工作原理。


我得到了一个类似的 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 而不仅仅是一个 属性 会很简单。我哪里错了?

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

您似乎想要 details 数组中的单个值,所以我的回答只取第一个。这样的转换器会工作吗?它获取 JObject 并从 details 数组中挑选出一个值,然后将其转换为 Details class.

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 包含在设置对象的 Converters 列表中。

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

我从 here 那里得到了灵感,其中一位用户描述了取消 JsonContractJsonConverter 是如何恢复为默认值 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 只会被使用一次。