-1

I am receiving json data in the following format. I have no control over the json data.

{
    "response": {
        "status": 1,
        "httpStatus": 200,
        "data": {
            "Countries": {
                "818": {
                    "id": "818",
                    "code": "EG",
                    "name": "Egypt"
                },
                "414": {
                    "id": "414",
                    "code": "KW",
                    "name": "Kuwait"
                },
                "682": {
                    "id": "682",
                    "code": "SA",
                    "name": "Saudi Arabia"
                },
                "784": {
                    "id": "784",
                    "code": "AE",
                    "name": "United Arab Emirates"
                }
            },
            "Regions": [],
            "Cities": [],
            "Exclude": []
        },
        "errors": [],
        "errorMessage": null
    }
}

I am trying to deserialize the data in order to get the country codes. I used a Dictionary to handle the country id part as the key and a class to handle the other values.

public class CountryResponseData
{
    public CountryData Data { get; set; }
}

public class CountryData
{
    public Dictionary<string, Country> Countries { get; set; }
}

public class Country
{
    public string Code { get; set; }
}

This works fine as long as their are countries in the data, but some of the data is in the following format.

{
    "response": {
        "status": 1,
        "httpStatus": 200,
        "data": {
            "Countries": [],
            "Regions": [],
            "Cities": [],
            "Exclude": []
        },
        "errors": [],
        "errorMessage": null
    }
}

Because the countries are an array instead of an object in this case the deserializer doesn't know how to handle it.

I have tried using a custom json converter like the ones described in these answers (https://stackoverflow.com/a/45505097/16431696 and https://stackoverflow.com/a/48794588/16431696), but it doesn't seem to do anything.

Any help would be much appreciated.

ma01
  • 3
  • 2

2 Answers2

2

I believe code from answers you're referring to, is working. I tested your case like this:

public class CountryResponseData
{
    public CountryData Data { get; set; }
}

public class CountryData
{
    [JsonConverter(typeof(EmptyArrayOrDictionaryConverter))]
    public Dictionary<string, Country> Countries { get; set; }
}

public class Country
{
    public string Code { get; set; }
}

// this a modified version of this SO-answer: https://stackoverflow.com/a/45505097/14072498
public class EmptyArrayOrDictionaryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) => objectType.IsAssignableFrom(typeof(Dictionary<string, object>));

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader);
        return token.Type switch
        {
            JTokenType.Object => token.ToObject(objectType, serializer), JTokenType.Array when !token.HasValues => Activator.CreateInstance(objectType),
            _ => throw new JsonSerializationException("Object or empty array expected")
        };
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => serializer.Serialize(writer, value);
}

public class MyJsonTestClass
{
    private const string JsonWithCountries = @"{
            ""data"": {
                ""Countries"": {
                    ""818"": {
                        ""id"": ""818"",
                        ""code"": ""EG"",
                        ""name"": ""Egypt""
                    },
                    ""414"": {
                        ""id"": ""414"",
                        ""code"": ""KW"",
                        ""name"": ""Kuwait""
                    },
                    ""682"": {
                        ""id"": ""682"",
                        ""code"": ""SA"",
                        ""name"": ""Saudi Arabia""
                    },
                    ""784"": {
                        ""id"": ""784"",
                        ""code"": ""AE"",
                        ""name"": ""United Arab Emirates""
                    }
                },
                ""Regions"": [],
                ""Cities"": [],
                ""Exclude"": []
            }
        }";

    private const string JsonWithoutCountries = @"{
            ""data"": {
                ""Countries"": [],
                ""Regions"": [],
                ""Cities"": [],
                ""Exclude"": []
            }
        }";

    [Test]
    public void MyJsonTest()
    {
        // START tests using NewtonSoft
        
        var result = JsonConvert.DeserializeObject<CountryResponseData>(JsonWithCountries);
        Assert.NotNull(result?.Data);

        var result2 = JsonConvert.DeserializeObject<CountryResponseData>(JsonWithoutCountries);
        Assert.NotNull(result2?.Data);
    }
}
Roar S.
  • 8,103
  • 1
  • 15
  • 37
  • I have implemented this answer, but when I put break points into the custom json converter it seems it isn't getting used, and so I am still getting the problem. – ma01 Jul 13 '21 at 16:45
  • @ma01: Just tested this, and breakpoint inside `ReadJson` is hit. Are you 100% sure you've implemented this in the same way as in this answer? – Roar S. Jul 13 '21 at 17:05
  • 1
    I got it working. I had `using System.Text.Json.Serialization` on the file with the property instead of `using Newtonsoft.Json`. I'm an idiot. Thank you for your help @Roar S. – ma01 Jul 13 '21 at 17:18
1

The easiest way is to replace Country[] by null in json string. The same code will work for both jsons

var jsonOriginal = "{\"response\":{\"status\":1,\"httpStatus\":200,\"data\":{\"Countries\":[],\"Regions\":[],\"Cities\":[],\"Exclude\":[]},\"errors\":[],\"errorMessage\":null}}";

var json=jsonOriginal.Replace("\"Countries\":[],","\"Countries\":null,");
// you can add the same for Regions and Cities too if one day they will be changed to dictionary type

you will have this output

{"response":{"status":1,"httpStatus":200,"data":{"Countries":null,"Regions":[],"Cities":[],"Exclude":[]},"errors":[],"errorMessage":null}}

But if for some reasons it doesn't fit you there is a hard way:

this code was tested in visual studio and works for me

var json = "{\"response\":{\"status\":1,\"httpStatus\":200,\"data\":{\"Countries\":[],\"Regions\":[],\"Cities\":[],\"Exclude\":[]},\"errors\":[],\"errorMessage\":null}}";

    JsonObj jsonObj = null;
    JsonObj2 jsonObj2 = null;

    try
    {
        jsonObj = JsonConvert.DeserializeObject<JsonObj>(json);
    }
    catch (Exception ex)
    {
        if (ex.Source == "Newtonsoft.Json")
        {

            try
            {
                jsonObj2 = JsonConvert.DeserializeObject<JsonObj2>(json);

            }

            catch (Exception ex1)
            {

                throw;
            }
        }

    }

    if (jsonObj != null) ....
    else ....

classes

public class Country
{
    public string id { get; set; }
    public string code { get; set; }
    public string name { get; set; }
}


public class Data
{
    public Dictionary<string, Country> Countries { get; set; }
    public List<object> Regions { get; set; }
    public List<object> Cities { get; set; }
    public List<object> Exclude { get; set; }
}
public class Data2
{
    public List<object> Countries { get; set; }
    public List<object> Regions { get; set; }
    public List<object> Cities { get; set; }
    public List<object> Exclude { get; set; }
}


public class Response
{
    public int status { get; set; }
    public int httpStatus { get; set; }
    public Data data { get; set; }
    public List<object> errors { get; set; }
    public object errorMessage { get; set; }
}
public class Response2
{
    public int status { get; set; }
    public int httpStatus { get; set; }
    public Data2 data { get; set; }
    public List<object> errors { get; set; }
    public object errorMessage { get; set; }
}

public class JsonObj
{
    public Response response { get; set; }
}


public class JsonObj2
{
    public Response2 response { get; set; }
}
Serge
  • 40,935
  • 4
  • 18
  • 45
  • Hi Serge. In this case, you're using exceptions to control the flow, this is usually considered as bad practice. BR – Roar S. Jul 12 '21 at 15:45
  • 1
    @RoarS. IMHO it depends on how offen the exeption will hapend. But I am agree if it is in many cases converter would be better. But it seems to me that it will be just an exeption. – Serge Jul 12 '21 at 15:47
  • Serge: With code from the link below, it is possible to code like this: `if (value.TryParseJson(out MyType result))`. https://stackoverflow.com/a/51428508/14072498 – Roar S. Jul 12 '21 at 16:13
  • I would be more worried about regions or cities. They can became a dictionary type too. Otherwise I don't see much sense to add empty arrays. – Serge Jul 12 '21 at 16:17
  • The string replace solved my issue. Pragmatic and simple :) – NobleGuy Jan 31 '23 at 11:37