0

I have following json, I am trying to Deserialize it. Tricky part is that in one node with name images in some data it is coming as array and in some it is coming as json object.. so how I can Deserialize both at a time.

This is JSON format.

{
  "variants": [
    {
      "name": "test1",
      "description": "test1",
      "images": []
    },
    {
      "name": "test2",
      "description": "test2",
      "images": {
        "WHITE": [
          {
            "id": "ttest",
            "product_id": "ttest"
          }
        ]
      }
    }
  ]
}

Now as you see here in images in one we have an array and in one we have json object.

I have tried below.

public List<Variant> variants { get; set; }

public class Variant
    {
        public string name { get; set;}
        public string description { get; set;}
        public List<Image> images { get; set; }
    }

public class Image
    {
        public List<White> White { get; set; }
    }

public class White
    {
        public string id { get; set;}
        public string product_id { get; set;}
    }

Then below line to Deserialize:

var items = JsonConvert.DeserializeObject<TestClass>(content);

dbc
  • 104,963
  • 20
  • 228
  • 340
Naw
  • 25
  • 4
  • What does TestClass look like? – Mark L May 17 '23 at 21:48
  • The above all properties are in TestClass – Naw May 17 '23 at 21:51
  • 2
    Your JSON is malformed: 1) Missing start braces `{` and `}`. 2) Missing starting double quote for `"name": test1"`. 3) Missing both double quotes for `"id": ttest,`. Can you confirm that your JSON is in fact well-formed? – dbc May 17 '23 at 21:52
  • Yes my json is well formatted.. I have copy past and then rename values in question so it looks like that – Naw May 17 '23 at 21:55
  • 1
    @Naw - it's fine to rename the values to obscure private data, but please make sure that the simplified JSON is well-formed. If you don't, we can't easily use it as a [mcve] and test it because it can't be parsed by any JSON parser. I'll fix it this time... – dbc May 17 '23 at 22:00

1 Answers1

1

The value for images seems to be either:

  • An object corresponding to a dictionary of lists of image data for arbitrary colors, here "WHITE".
  • An empty array when there are no colors values.

Since the value of images is sometimes an array and sometimes an object, you will need to deserialize it using an appropriate custom JsonConverter<T>.

Specifically, define the following data model:

public class TestClass
{
    public List<Variant> variants { get; set; } = new ();
}

public class Variant
{
    public string name { get; set; }
    public string description { get; set; }
    
    [JsonConverter(typeof(JsonSingleOrEmptyArrayConverter<Dictionary<string, List<Image>>>))]
    public Dictionary<string, List<Image>> images { get; set; } = new ();
}

public class Image
{
    public string id { get; set; }
    public string product_id { get; set; }
}

Where JsonSingleOrEmptyArrayConverter<T> is copied verbatim from this answer to Cannot Deserialize the Current JSON Object (Empty Array):

public class JsonSingleOrEmptyArrayConverter<T> : JsonConverter where T : class
{
    // Copied from this answer https://stackoverflow.com/a/53219877
    // To https://stackoverflow.com/questions/53219668/cannot-deserialize-the-current-json-object-empty-array
    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var contract = serializer.ContractResolver.ResolveContract(objectType);
        // Allow for dictionary contracts as well as objects contracts, since both are represented by 
        // an unordered set of name/value pairs that begins with { (left brace) and ends with } (right brace).
        if (!(contract is Newtonsoft.Json.Serialization.JsonObjectContract 
              || contract is Newtonsoft.Json.Serialization.JsonDictionaryContract))
        {
            throw new JsonSerializationException(string.Format("Unsupported objectType {0} at {1}.", objectType, reader.Path));
        }

        switch (reader.SkipComments().TokenType)
        {
            case JsonToken.StartArray:
                {
                    int count = 0;
                    while (reader.Read())
                    {
                        switch (reader.TokenType)
                        {
                            case JsonToken.Comment:
                                break;
                            case JsonToken.EndArray:
                                // You might want to allocate an empty object here if existingValue is null
                                // If so, do
                                // return existingValue ?? contract.DefaultCreator();
                                return existingValue;
                            default:
                                {
                                    count++;
                                    if (count > 1)
                                        throw new JsonSerializationException(string.Format("Too many objects at path {0}.", reader.Path));
                                    existingValue = existingValue ?? contract.DefaultCreator();
                                    serializer.Populate(reader, existingValue);
                                }
                                break;
                        }
                    }
                    // Should not come here.
                    throw new JsonSerializationException(string.Format("Unclosed array at path {0}.", reader.Path));
                }

            case JsonToken.Null:
                return null;

            case JsonToken.StartObject:
                existingValue = existingValue ?? contract.DefaultCreator();
                serializer.Populate(reader, existingValue);
                return existingValue;

            default:
                throw new InvalidOperationException("Unexpected token type " + reader.TokenType.ToString());
        }
    }

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

public static partial class JsonExtensions
{
    public static JsonReader SkipComments(this JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            ;
        return reader;
    }
}

And now you will be able to deserialize your JSON by simply doing:

var root = JsonConvert.DeserializeObject<TestClass>(json);

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340