Search code examples
c#jsonparsinghttpclientjson-deserialization

HttpClient parse JSON object to List, instead of JSON Array


One of our customer require some data, which should be obtained from a service provider. the service is written in PHP, and is more like a web api than a soap or wcf service, and the response is in following format:

{"0":{"Code":"AL","Name":"ALBANIA"},"1":{"Code":"DZ","Name":"ALGERIA"},"2":{"Code":"AD","Name":"ANDORRA"},"3":{"Code":"AO","Name":"ANGOLA"},"4":{"Code":"AI","Name":"ANGUILLA"},"5":{"Code":"AG","Name":"ANTIGUA"},"6":{"Code":"AR","Name":"ARGENTINA"},"7":{"Code":"AM","Name":"ARMENIA"},"8":{"Code":"AW","Name":"ARUBA"},"9":{"Code":"AU","Name":"AUSTRALIA"},"10":{"Code":"AT","Name":"AUSTRIA"},"11":{"Code":"AZ","Name":"AZERBAIJAN"},"12":{"Code":"BS","Name":"BAHAMAS"},"StartTime":"2016-06-13 04:57:15","EndTime":"2016-06-13 04:57:15"}

As you can see it's an array but in a format of an object, that's what cause me issues.

I use HttpClient and my model is like this:

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

    public string Name { get; set; }
}

i also extend it to make it part of following model:

public class CountryResponseVM
{
    public List<CountryVM> CountryVMs { get; set; }
    public string StartTime { get; set; }
    public string EndTime { get; set; }
}

when i run the following code:

using (var client = new HttpClient())
{
    var response = client.PostAsync(command, new StringContent(string.Empty)).Result;
    if (response.IsSuccessStatusCode)
    {
        List<CountryVM> readAsAsync = response.Content.ReadAsAsync<List<CountryVM>>().Result;
    }
}

Either with 'CountryVM' or with 'CountryResponseVM' class, it throw following exception:

An exception of type 'System.Net.Http.UnsupportedMediaTypeException' occurred in System.Net.Http.Formatting.dll but was not handled in user code

Additional information: No MediaTypeFormatter is available to read an object of type 'List`1' from content with media type 'text/html'.

How can i reformat the response, or parse json object as an array before calling ReadAsAsync method.


UPDATE

I also have another model, The city which is listed under countries. The city model seem to be more right, it has array instead of object, and gave the array a name, but still i have same issue with it as i had with the countries, in all cases.

Response:

{"CityInfo":[{"CityCode":"TIA-","Name":"Albania"},{"CityCode":"TIA-7","Name":"Berat"},{"CityCode":"TIA-3","Name":"Durres"},{"CityCode":"TIA-4","Name":"Korce"},{"CityCode":"TIA-8","Name":"Pogradec"},{"CityCode":"TIA-2","Name":"Sarande"},{"CityCode":"TIA-6","Name":"Shkoder"},{"CityCode":"TIA-1","Name":"Tirana"},{"CityCode":"TIA-5","Name":"Vlore"}],"StartTime":"2016-06-13 06:03:34","EndTime":"2016-06-13 06:03:34"}

Models:

public class CityResponseVM
{
    public List<CityVM> CityInfo { get; set; }
    public string StartTime { get; set; }
    public string EndTime { get; set; }
}

public class CityVM
{
    public string CityCode { get; set; }
    public string Name { get; set; }
}

And Request:

string command = Otrams.Url+Otrams.GetAction(ServiceAction.CityList) +"&username="+Otrams.Username+"&password="+Otrams.Password+"&gzip=no&country=AL";
using (var client = new HttpClient())
{
    var response = client.PostAsync(command, new StringContent(string.Empty)).Result;
    if (response.IsSuccessStatusCode)
    {
        //CityResponseVM readAsAsync = response.Content.ReadAsAsync<CityResponseVM>().Result;
        var rawData = response.Content.ReadAsStringAsync().Result;
        var myList = JsonConvert.DeserializeObject<IEnumerable<CityVM>>(rawData);
    }
}

Solution

  • Use two objects, one for deserializing, and one to actually contain the result.

    public class MyTempModel
    {
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
        [JsonExtensionData]
        public Dictionary<string, object> Countries { get; set; }
    }
    
    public class MyRealModel : Dictionary<int, CountryVM>
    {
        public DateTime? StartTime { get; set; }
        public DateTime? EndTime { get; set; }
    }
    

    Deserialize:

    var myList = JsonConvert.DeserializeObject<MyTempModel>(jsonResult);
    
    var model = new MyRealModel
    {
        StartTime = myList.StartTime,
        EndTime = myList.EndTime
    };
    
    foreach (var temp in myList.Countries)
    {
        // Deserialize the actual ContryVm.
        var obj = JsonConvert.DeserializeObject<CountryVM>(temp.Value.ToString());
        int key = 0;
        int.TryParse(temp.Key, out key);
        model.Add(key, obj);
    }
    

    The key component here is to use JsonExtensionData, as suggested here: How to serialize a Dictionary as part of its parent object using Json.Net. It will enable the dictionary format, with extra properties such as StartTime and EndTime.

    A more advanced solution would be to use a JsonConverter.

    See chat for more info.