C# 中反序列化可以是数组或单个对象的 json 对象的最佳方法

本文关键字:对象 单个 json 方法 最佳 数组 反序列化 | 更新日期: 2023-09-27 18:32:24

我们正在处理一个json API结果。只是为了让我们的生活变得困难,如果有多个对象,API 的提供者会将项目作为数组返回,如果只有一个对象,则作为单个对象返回。

例如

如果只有一个对象...

{
  propertyA: {
    first: "A",
    second: "B"
  }
}

或者,如果有多个:

{
  propertyA: [
    {
      first: "A",
      second: "B"
    },
    {
      first: "A",
      second: "B"
    }
  ]
}

有没有人有处理这种情况的好方法?理想情况下,我们希望将两者序列化为

public class ApiResult{
  public ApiItem[] PropertyA {get;set;}
}

这适用于第二个示例,但是在遇到第一个示例时,您会在System.Web.Extensions中出现类型为"System.MissingMethodException"的第一次机会异常.dll

其他信息:没有为"ApiItem[]"类型定义无参数构造函数。

C# 中反序列化可以是数组或单个对象的 json 对象的最佳方法

我假设类定义如下

public class ApiResult
{
    public ApiItem[] PropertyA { get; set; }
}
public class ApiItem
{
    public string First { get; set; }
    public string Second { get; set; }
}

您可以将 json 反序列化为动态变量,然后检查d.propertyA的类型。如果是JArraypropertyA是一个数组,因此您可以将 json 反序列化为ApiResult。如果是JObjectpropertyA是单个对象,因此您需要手动构造ApiItem并将其分配给ApiResult PropertyA。考虑以下方法

public ApiResult Deserialize(string json)
{
    ApiResult result = new ApiResult();
    dynamic d = JsonConvert.DeserializeObject(json);
    if (d.propertyA.GetType() == typeof (Newtonsoft.Json.Linq.JObject))
    {
        // propertyA is a single object, construct ApiItem manually
        ApiItem item = new ApiItem();
        item.First = d.propertyA.first;
        item.Second = d.propertyA.second;
        // assign item to result.PropertyA[0]
        result.PropertyA = new ApiItem[1];
        result.PropertyA[0] = item;
    }
    else if (d.propertyA.GetType() == typeof (Newtonsoft.Json.Linq.JArray))
    {
        // propertyA is an array, deserialize json into ApiResult
        result = JsonConvert.DeserializeObject<ApiResult>(json);
    }
    return result;
}

上面的代码将为两个 json 示例返回一个 ApiResult 实例。

工作演示:https://dotnetfiddle.net/wBQKrp

基于ekad的回答,我编写了代码:

  • 更短:因为现在您没有 ApiItem 中的每个属性一一映射
  • 在已经是数组的情况下可能会更快(通过不使用相同的 json 字符串输入调用两个版本的JsonConvert.DeserializeObject
  • 明确说明如何引入其他属性
  • 处理我们propertyA的未知类型的错误情况(可以是数组或对象)。

请注意,我调用 JObject.Parse 而不是 JsonConvert.DeserializeObject ,然后只ToObject<>该特定情况下我需要的部分:

static ApiResult Deserialize(string json)
{
    JObject j = JObject.Parse(json);
    var propA = j["propertyA"];
    switch (propA.Type.ToString())
    {
        case "Object":
            return new ApiResult {
                PropertyA = new[]{propA.ToObject<ApiItem>()},
                SomethingElse = j["somethingElse"].ToObject<string>(),
            };
        case "Array":
            return j.ToObject<ApiResult>();
        default:
            throw new Exception("Invalid json with propertyA of type " + propA.Type.ToString());
    }
}

API 几乎相同,但我添加了SomethingElse(用于展示如何使用此方法轻松处理其他属性):

public class ApiResult
{
    public ApiItem[] PropertyA { get; set; }
    public string SomethingElse { get; set; }
}
public class ApiItem
{
    public string First { get; set; }
    public string Second { get; set; }
}

工作演示:https://dotnetfiddle.net/VLbTMu

JSON# 有一个非常轻量级的工具,可以让你实现这一点。它将从较大的 JSON 对象中检索嵌入式 JSON,无论嵌入式 JSON 是对象还是数组:

const string schoolMetadata = @"{ "school": {...";
var jsonParser = new JsonObjectParser();
using (var stream = 
    new MemoryStream(Encoding.UTF8.GetBytes(schoolMetadata))) {
    Json.Parse(_jsonParser, stream, "teachers");
}

在这里,我们从一个更大的"学校"对象中检索一个"教师"对象。

序列化/反序列化到/从 JSON 反序列化的最佳方法是 Json.NET

流行的 .NET 高性能 JSON 框架

Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Sizes = new string[] { "Small" };
string json = JsonConvert.SerializeObject(product);
//{
//  "Name": "Apple",
//  "Expiry": "2008-12-28T00:00:00",
//  "Sizes": [
//    "Small"
//  ]
//}
string json = @"{
  'Name': 'Bad Boys',
  'ReleaseDate': '1995-4-7T00:00:00',
  'Genres': [
    'Action',
    'Comedy'
  ]
}";
Movie m = JsonConvert.DeserializeObject<Movie>(json);
string name = m.Name;
// Bad Boys