Cannot deserialize JSON array into type - Json.NET

42,601

Solution 1

You have to write a custom JsonConverter:

    public class CountryModelConverter : JsonConverter
    {

        public override bool CanConvert(Type objectType)
        {
            if (objectType == typeof(CountryModel))
            {
                return true;
            }

            return false;
        }

        public override object ReadJson(JsonReader reader, Type objectType
            , object existingValue, JsonSerializer serializer)
        {
            reader.Read(); //start array
            //reader.Read(); //start object
            JObject obj = (JObject)serializer.Deserialize(reader);

            //{"page":1,"pages":1,"per_page":"50","total":35}
            var model = new CountryModel();

            model.Page = Convert.ToInt32(((JValue)obj["page"]).Value);
            model.Pages = Convert.ToInt32(((JValue)obj["pages"]).Value);
            model.Per_Page = Int32.Parse((string) ((JValue)obj["per_page"]).Value);
            model.Total = Convert.ToInt32(((JValue)obj["total"]).Value);

            reader.Read(); //end object

            model.Countries = serializer.Deserialize<List<Country>>(reader);

            reader.Read(); //end array

            return model;
        }

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

And tag the CountryModel with that converter (I also had to switch some int to string):

    [JsonConverter(typeof(CountryModelConverter))]
    public class CountryModel
    {
        public int Page { get; set; }
        public int Pages { get; set; }
        public int Per_Page { get; set; }
        public int Total { get; set; }

        public List<Country> Countries { get; set; }
    }

    public class Country
    {
        public string Id { get; set; }
        public string Iso2Code { get; set; }
        public string Name { get; set; }
        public Region Region { get; set; }
    }

    public class Region
    {
        public string Id { get; set; }
        public string Value { get; set; }
    }

Then you should be able to deserialize like this:

var output = JsonConvert.DeserializeObject<CountryModel>(result);

Solution 2

This looks like a (not very good) attempt at representing XML in JSON. The JSON looks like this:

[
  {
    "page": 1,
    …
  },
  [
    {
      "id": "AFG",
      "name": "Afghanistan",
      …
    },
    {
      "id": "BDI",
      "name": "Burundi",
      …
    },
    …
  ]
]

While a reasonable JSON (that would incidentally map to your model nicely) would look like this:

{
  "page": 1,
  …,
  "countries": [
    {
      "id": "AFG",
      "name": "Afghanistan",
      …
    },
    {
      "id": "BDI",
      "name": "Burundi",
      …
    },
    …
  ]
}

If you are sure you want to use JSON (and not XML), you can do it by first deserializing the JSON into JSON.NET's object model and then deserialize that into your model:

var json = client.DownloadString("http://api.worldbank.org/incomeLevels/LIC/countries?format=json");

var array = (JArray)JsonConvert.DeserializeObject(json);

var serializer = new JsonSerializer();

var countryModel = serializer.Deserialize<CountryModel>(array[0].CreateReader());

countryModel.Countries = serializer.Deserialize<List<Country>>(array[1].CreateReader());

return countryModel;

Don't forget to change your Id properties to string, because that's what they are.

Share:
42,601
tugberk
Author by

tugberk

Senior Software Engineer and Tech Lead, with a growth mindset belief and 10+ years of practical software engineering experience including technical leadership and distributed systems. I have a passion to create impactful software products, and I care about usability, reliability, observability and scalability of the software systems that I work on, as much as caring about day-to-day effectiveness, productivity and happiness of the team that I work with. I occasionally speak at international conferences (tugberkugurlu.com/speaking), and write technical posts on my blog (tugberkugurlu.com). I currently work at Facebook as a Software Engineer. I used to work at Deliveroo as a Staff Software Engineer in the Consumer division, working on distributed backend systems which have high throughput, low latency and high availability needs. Before that, I used to work at Redgate as a Technical Lead for 4 years, where I led and line-managed a team of 5 Software Engineers. I was responsible for all aspects of the products delivered by the team from technical architecture to product direction. I was also a Microsoft MVP for 7 years between 2012-2019 on Microsoft development technologies.

Updated on February 27, 2020

Comments

  • tugberk
    tugberk over 3 years

    I am trying to deserialize a json data into a model class but I am failing. Here is what I do:

        public CountryModel GetCountries() {
    
            using (WebClient client = new WebClient()) {
    
                var result = client.DownloadString("http://api.worldbank.org/incomeLevels/LIC/countries?format=json");
    
                var output = JsonConvert.DeserializeObject<List<CountryModel>>(result);
    
                return output.First();
            }
        }
    

    This is how my model looks like:

    public class CountryModel
    {
        public int Page { get; set; }
        public int Pages { get; set; }
        public int Per_Page { get; set; }
        public int Total { get; set; }
    
        public List<Country> Countries { get; set; }
    }
    
    public class Country
    {
        public int Id { get; set; }
        public string Iso2Code { get; set; }
        public string Name { get; set; }
        public Region Region { get; set; }
    }
    
    public class Region
    {
        public int Id { get; set; }
        public string Value { get; set; }
    }
    

    You can see the Json I am getting here: http://api.worldbank.org/incomeLevels/LIC/countries?format=json

    This is the error I get:

    Cannot deserialize JSON array into type 'Mvc4AsyncSample.Models.CountryModel'. Line 1, position 1.

  • svick
    svick over 11 years
    Can't you simplify deserializing the properties of CountryModel by using serializer.Deserialize<CountryModel>(reader)?
  • Paul Tyng
    Paul Tyng over 11 years
    Much simpler than my version, nice, didn't know you could create the reader's off the built in objects.
  • Paul Tyng
    Paul Tyng over 11 years
    svick's answer is better if you only need to read, the JsonConverter is only really necessary if you have to go both ways.
  • Paul Tyng
    Paul Tyng over 11 years
    @svick I thought about that, the problem is that CountryModel has the attribute, so it gets in to infinite loop, it would be simplified if the object model changed but I was trying to keep to his object model.
  • svick
    svick over 11 years
    Right, I didn't think about that.
  • tugberk
    tugberk over 11 years
    this does not effect the deserialization process.