解析数组和单个对象

Parsing both Array and single object

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

如您所见,有时充电器对象 return 带有数组,有时则没有。我的问题是如何将其解析为 C# class 对象?如果我像下面那样添加了 C# class,它就无法正确解析 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);

错误

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[Charge]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path 'Pricing[0].charges.code', line 1, position 69.

在使用 C# 接收不同类型的对象类型时,是否有任何常用的解析方法?

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

您可以尝试将 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; }
    }
    }
}

好的,这里有另一种方法可以做到这一点,而不必使用两个 classes 并且没有 try catch。基本上只需将 Pricing class 更新为 following,它适用于这两种情况。可能是更好的方法,但这更好(至少在我看来)并且有两个 classes 并让 try catch 做你的分支。如果您有 10 个属性存在这个问题,那么您会创建 10 个吗? class是否可以处理每个组合?没办法哈哈!

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

另一种处理此问题的方法是定义一个可以处理这两种情况的自定义 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<List<T>>ToObject<T>
  • CanConvert 中,我们检查要填充的 属性 类型是 List<T>
    • 尽管有一个通用的 JsonConverter<T>,您不必在其中定义 CanConvert,但它的 ReadJson 可以以更复杂的方式实现
  • 因为问题是关于反序列化我还没有实现 WriteJson 方法
    • 您也可以考虑将基数 class 的 CanWrite 属性 覆盖为始终 return false

有了这个 class 在我们手中,您可以用 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; } 
    
    ...
}