2

API retuned json object like below 2 forms.

Form 1

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

Form 2

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

As you can see sometime chargers object return with array and some times not. My question is how to parse this to C# class object? If I added the C# class like below it cannot be parse properly for Form 2. (Form 1 parsing properly)

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

Error occurred when parse with Newtonsoft deserialization.

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

Error

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.

Any common method for parsing, when receiving different type of object types with C#?

(I look into this as well but it's for java. And most of this kind of question raised for java but not C#.)

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
weeraa
  • 1,123
  • 8
  • 23
  • 40

3 Answers3

5

Yet another way of dealing with this problem is to define a custom JsonConverter which can handle both cases.

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(); 
}
  • Inside the ReadJson first we get a JToken to be able to determine the read value's Type (kind)
    • Based on that the we can either call ToObject<List<T>> or ToObject<T>
  • Inside the CanConvert we examine that the to be populated property's type is a List<T>
    • Even though there is a generic JsonConverter<T> where you don't have to define the CanConvert, its ReadJson can be implemented in a bit more complicated way
  • Since the question is all about deserialization I've not implemented the WriteJson method
    • You might also consider to override the CanWrite property of the base class to always return false

With this class in our hand you can decorate your properties with a JsonConverterAttribute to tell to the Json.NET how to deal with those properties

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

    [JsonConverter(typeof(ArrayOrObjectConverter<Charge>))]
    public List<Charge> charges { get; set; } 
    
    ...
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • 1
    WoW... WoW.... WoW... Thanks @Peter. This was the answer that I needed. You saved my time. – weeraa Jan 26 '22 at 08:43
2

You could go for an approach to try and parse Form1's object to json, if it fails it will use Form2's object to json.

Example here and below: 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; }
    }
    }
}
Kye
  • 76
  • 1
  • 10
  • So this technically would work but what if there were 10 properties with this issue? You would need 10! classes. See my solution below for a more dynamic way – Maxqueue Jan 26 '22 at 07:32
  • Thanks for the reply @Kye. This is not gonna work for me since there are a lot of object like above. (I added only little part of the json) Sometime "chargers" attribute act as list some times not. There are few other objects as well. Is there any common mechanism for doing this? – weeraa Jan 26 '22 at 07:36
  • 1
    Exactly @Maxqueue. Honestly there are many more objects in my json. That's really a large json. – weeraa Jan 26 '22 at 07:37
  • If there are more objects in Form1's `charges` array, this would support it. @Maxqueue – Kye Jan 26 '22 at 08:53
2

Ok so here is another way to do this without having to use two classes and not having a try catch. Basically just update Pricing class to following and it works for both cases. Probably a better way but this is better (at least in my opinion) and having two classes and having try catch do your branching. If you had ten properties with this issue would you then create 10! classes to handle every combo? No way lol!

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);
            }
        }
    }
}
Maxqueue
  • 2,194
  • 2
  • 23
  • 55