【问题标题】:Parsing both Array and single object解析数组和单个对象
【发布时间】:2022-01-26 06:48:16
【问题描述】:

API 重新调整 json 对象,如下 2 种形式。

表格 1

{
"Pricing": [
    {
        "total": 27,
        "currency": "USD",
        "charges": [ //Chargers Array
            {
                "code": "C1",
                "currency": "USD",
                "rate": 15
            },
            {
                "code": "C45",
                "currency": "USD",
                "rate": 12
            }
        ]
    }
  ]
}

表格 2

{
"Pricing": [
    {
        "total": 12,
        "currency": "USD",
        "charges": { //Chargers single object
            "code": "C1",
            "currency": "USD",
            "rate": 12
        }
    }
  ]
}

正如您所见,充电器对象有时会返回数组,有时则不会。我的问题是如何将其解析为 C# 类对象?如果我添加了如下所示的 C# 类,则无法正确解析 Form 2。(正确解析 Form 1)

public class Charge
{
    public string code { get; set; }
    public string currency { get; set; }
    public decimal rate { get; set; }
}

public class Pricing
{
    public decimal total { get; set; }
    public string currency { get; set; }
    public List<Charge> charges { get; set; } //In Form 2, this should be single object
}

public class MainObj
{
    public List<Pricing> Pricing { get; set; }
}

使用 Newtonsoft 反序列化解析时发生错误。

MainObj obj = JsonConvert.DeserializeObject<MainObj>(json);

错误

无法反序列化当前 JSON 对象(例如 {"name":"value"}) 进入类型 'System.Collections.Generic.List`1[Charge]' 因为类型 需要一个 JSON 数组(例如 [1,2,3])才能正确反序列化。修理 此错误将 JSON 更改为 JSON 数组(例如 [1,2,3])或 更改反序列化类型,使其成为普通的 .NET 类型(例如 不是像整数这样的原始类型,不是像数组这样的集合类型 或列表)可以从 JSON 对象反序列化。 JsonObjectAttribute 也可以添加到类型中以强制它 从 JSON 对象反序列化。路径“定价 [0].charges.code”,行 1,位置 69。

在用C#接收不同类型的对象类型时,有什么常用的解析方法吗?

(我也研究了this,但它是针对java的。大多数此类问题是针对java而不是C#提出的。)

【问题讨论】:

  • 为每个表单创建两个类
  • 这行不通@Fabio,因为我的完整 json 对象具有更多属性,例如“充电器”。
  • 与 API 提供商交谈以始终发送数组,这将简化这两项工作:)
  • 或者为定价类编写自己的序列化程序来处理这两种情况。
  • API 提供的公司与我们相比是如此之大,他们将花费大量时间来实施它。 :(

标签: c# parsing


【解决方案1】:

解决此问题的另一种方法是定义一个自定义的JsonConverter,它可以处理这两种情况。

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

    public override bool CanConvert(Type objectType)
        => objectType == typeof(List<T>);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        =>throw new NotImplementedException(); 
}
  • ReadJson 内部,首先我们得到一个JToken,以便能够确定读取值的Type(种类)
    • 基于此,我们可以调用ToObject&lt;List&lt;T&gt;&gt;ToObject&lt;T&gt;
  • CanConvert 中,我们检查要填充的属性的类型是List&lt;T&gt;
    • 尽管有一个通用的JsonConverter&lt;T&gt;,您不必定义CanConvert,但它的ReadJson 可以以更复杂的方式实现
  • 因为问题都是关于反序列化的,所以我没有实现WriteJson 方法
    • 您还可以考虑覆盖基类的CanWrite 属性以始终返回false

有了这个类,你可以用JsonConverterAttribute 来装饰你的属性,告诉 Json.NET 如何处理这些属性

public class Pricing
{
    public decimal total { get; set; }
    public string currency { get; set; }

    [JsonConverter(typeof(ArrayOrObjectConverter<Charge>))]
    public List<Charge> charges { get; set; } 
    
    ...
}

【讨论】:

  • 哇...哇...哇...谢谢@Peter。这就是我需要的答案。你节省了我的时间。
【解决方案2】:

您可以尝试将 Form1 的对象解析为 json,如果失败,它将使用 Form2 的对象到 json。

此处和下方的示例:https://dotnetfiddle.net/F1Yh25

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
public class Program
{
    public static void Main()
    {
        string form1 = "{\"Pricing\":[{\"total\":27,\"currency\":\"USD\",\"charges\":[{\"code\":\"C1\",\"currency\":\"USD\",\"rate\":15},{\"code\":\"C45\",\"currency\":\"USD\",\"rate\":12}]}]}";
        string form2 = "{\"Pricing\":[{\"total\":12,\"currency\":\"USD\",\"charges\":{\"code\":\"C1\",\"currency\":\"USD\",\"rate\":12}}]}";
        
        string json = form1;//Change form1 to form2 and it will also work
        try
        {
            Form1.Root Object = JsonConvert.DeserializeObject<Form1.Root>(json);
            Console.WriteLine("Item 1's Pricing: " + Object.Pricing[0].total);
        }
        catch//If Form1's json is Form2 it will catch here
        {
            Form2.Root Object = JsonConvert.DeserializeObject<Form2.Root>(json);
            Console.WriteLine("Item 1's Pricing: " + Object.Pricing[0].total);
        }
    }
    
    public class Form1
    {
        public class Charge
    {
        public string code { get; set; }
        public string currency { get; set; }
        public int rate { get; set; }
    }

    public class Pricing
    {
        public int total { get; set; }
        public string currency { get; set; }
        public List<Charge> charges { get; set; }
    }

    public class Root
    {
        public List<Pricing> Pricing { get; set; }
    }
        
    }
    
    public class Form2
    {
    public class Charges
    {
        public string code { get; set; }
        public string currency { get; set; }
        public int rate { get; set; }
    }

    public class Pricing
    {
        public int total { get; set; }
        public string currency { get; set; }
        public Charges charges { get; set; }
    }

    public class Root
    {
        public List<Pricing> Pricing { get; set; }
    }
    }
}

【讨论】:

  • 所以这在技术上是可行的,但是如果有 10 个属性存在这个问题呢?你需要10个!类。请参阅下面的解决方案以获得更动态的方式
  • 感谢@Kye 的回复。这对我不起作用,因为上面有很多对象。 (我只添加了 json 的一小部分)有时“充电器”属性充当列表,有时不是。其他对象也很少。有什么通用的机制可以做到这一点吗?
  • 正是@Maxqueue。老实说,我的 json 中有更多对象。这真的是一个很大的 json。
  • 如果Form1的charges数组中有更多的对象,这将支持它。 @Maxqueue
【解决方案3】:

好的,这是另一种无需使用两个类且无需尝试捕获的方法。基本上只需将定价类更新为以下,它适用于两种情况。可能是一种更好的方法,但这更好(至少在我看来)并且有两个类并让 try catch 进行分支。如果你有十个有这个问题的属性,你会创建 10 个!处理每个组合的类?没办法,哈哈!

public class Pricing {
    public int total { get; set; }
    public string currency { get; set; }
    private List<Charge> _charges;
    public object charges {
        get {
            return _charges;
        }
        set {
            if(value.GetType().Name == "JArray") {
                _charges = JsonConvert.DeserializeObject<List<Charge>>(value.ToString());
            }
            else {
                _charges = new List<Charge>();
                var charge = JsonConvert.DeserializeObject<Charge>(value.ToString());
                _charges.Add(charge);
            }
        }
    }
}

【讨论】:

  • object 作为收费类型是这种方法的最大缺陷
  • @Fabio 是的,有什么替代方案?
  • List&lt;Charge&gt; 带有自定义序列化程序,如果提供单个对象,它将在列表中包装单个对象
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-20
  • 2013-03-22
  • 1970-01-01
  • 1970-01-01
  • 2015-06-10
  • 2018-07-15
  • 2021-09-30
相关资源
最近更新 更多