首页 > 解决方案 > C# Newtonsoft JSON,处理可能包含数组或对象的响应

问题描述

我在尝试用 C# 解释来自 API 服务器的响应时遇到了一些麻烦。

这是我们在纯 JSON 中得到的 API 响应;

[
    {
        "response" : { "test" : "Value" }
    },
    {
        "response" : []
    }
]

如何让 Newtonsoft JSON 处理这样的响应?

目前,我将“响应”对象作为模型,一旦 Newtonsoft 使用JsonConvert.DeserialiseObject我处理内容,我就会收到以下错误。

Newtonsoft.Json.JsonSerializationException:无法将当前 JSON 数组(例如 [1,2,3])反序列化为类型“(已编辑)”,因为该类型需要 JSON 对象(例如 {"name":"value"})来反序列化正确。要修复此错误,请将 JSON 更改为 JSON 对象(例如 {"name":"value"})或将反序列化类型更改为数组或实现集合接口的类型(例如 ICollection、IList),例如可以从 JSON 数组反序列化。JsonArrayAttribute 也可以添加到类型中以强制它从 JSON 数组反序列化。

标签: c#jsonjson.net

解决方案


You need to inspect the responses one by one and handle them differently based on what they are.

You can load up the JSON dynamically, then deserialise them based on what got loaded.

i.e. Objects deserialised as objects, and arrays deserialised as arrays.

Here's an example of how you can achieve that:

.Net Fiddle of this working.

using System;
using  Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var json = "[{ \"response\" : { \"test\" : \"Value1\" } }, { \"response\" : [ { \"test\" : \"Value2\" }, { \"test\" : \"Value3\" }] }]";
        var responseContainers = JArray.Parse(json);

        foreach(var responseContainer in responseContainers)
        {
            var response = responseContainer.Value<JToken>("response");

            if(response.Type == JTokenType.Object)
            {
                var data = response.ToObject<Data>();
                Console.WriteLine("data: " + data.test);
            }
            else if(response.Type == JTokenType.Array)
            {
                var dataJArray = response.ToObject<JArray>();

                foreach(var dataJToken in dataJArray)
                {
                    var data = dataJToken.ToObject<Data>();
                    Console.WriteLine("datas: " + data.test);
                }
            }
        }
    }
}

public class Data
{
    public string test { get;set; }
}

Output:

data: Value1
datas: Value2
datas: Value3

Alternatively, you could use the SingleOrArrayConverter from this question.

.Net Fiddle

using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

public class Program
{
    public static void Main()
    {
        var json = "[{ \"response\" : { \"test\" : \"Value1\" } }, { \"response\" : [ { \"test\" : \"Value2\" }, { \"test\" : \"Value3\" }] }]";
        var items = JsonConvert.DeserializeObject<List<Item>>(json);

        foreach(var item in items)
        {
            var responses = item.response;

            Console.WriteLine("ResponseCount: " + responses.Count);

            foreach(var response in responses)
            {
                Console.WriteLine("response: " + response.test);
            }
        }
    }
}

public class Item
{
    [JsonConverter(typeof(SingleOrArrayConverter<Data>))]
    public List<Data> response { get;set; }
}

public class Data
{
    public string test { get;set; }
}

class SingleOrArrayConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(List<T>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Array)
        {
            return token.ToObject<List<T>>();
        }
        return new List<T> { token.ToObject<T>() };
    }

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

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

Output:

ResponseCount: 1
response: Value1
ResponseCount: 2
response: Value2
response: Value3

推荐阅读