1

I am having difficulty deserializing a JSON string retrieved from a web source. I receive an error stating the following:

Unhandled Exception: Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"} ) into type 'NewsApp.ComplexTypes.NYTimes.Keyword' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.

This also happens with a property named Byline. Here is the json2csharp models that were produced:

public class Headline
{
    public string main { get; set; }
    public string kicker { get; set; }
}

public class Keywords
{
    public string rank { get; set; }
    public string name { get; set; }
    public string value { get; set; }
}

public class Byline
{
    public string organization { get; set; }
    public string original { get; set; }
    public List<string> person { get; set; }
}

public class Multimedia
{
    public string url { get; set; }
    public string format { get; set; }
    public int height { get; set; }
    public int width { get; set; }
    public string type { get; set; }
    public string subtype { get; set; }
    public string caption { get; set; }
    public string copyright { get; set; }
}

public class Doc
{
    public string web_url { get; set; }
    public string snippet { get; set; }
    public string lead_paragraph { get; set; }
    public string @abstract { get; set; }
    public string print_page { get; set; }
    public List<string> blog { get; set; }
    public string source { get; set; }
    public Headline headline { get; set; }
    public Keywords keywords { get; set; }
    public string pub_date { get; set; }
    public string document_type { get; set; }
    public string news_desK { get; set; }
    public string section_name { get; set; }
    public string subsection_name { get; set; }
    public Byline byline { get; set; }
    public string type_of_material { get; set; }
    public string _id { get; set; }
    public string word_count { get; set; }
    public string slideshow_credits { get; set; }
    public List<Multimedia> multimedia { get; set; }
}

public class Meta
{
    public int hits { get; set; }
    public int time { get; set; }
    public int offset { get; set; }
}

public class Response
{
    public List<Doc> docs { get; set; }
    public Meta meta { get; set; }
}

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

And here is the structure of the JSON returned from the API call.

{
  "response": {
    "docs": [
      {
        "web_url": "string",
        "snippet": "string",
        "lead_paragraph": "string",
        "abstract": "string",
        "print_page": "string",
        "blog": [
          ""
        ],
        "source": "string",
        "headline": {
          "main": "string",
          "kicker": "string"
        },
        "keywords": {
          "rank": "string",
          "name": "string",
          "value": "string"
        },
        "pub_date": "string",
        "document_type": "string",
        "news_desK": "string",
        "section_name": "string",
        "subsection_name": "string",
        "byline": {
          "organization": "string",
          "original": "string",
          "person": [
            ""
          ]
        },
        "type_of_material": "string",
        "_id": "string",
        "word_count": "string",
        "slideshow_credits": "string",
        "multimedia": [
          {
            "url": "string",
            "format": "string",
            "height": 0,
            "width": 0,
            "type": "string",
            "subtype": "string",
            "caption": "string",
            "copyright": "string"
          }
        ]
      }
    ],
    "meta": {
      "hits": 0,
      "time": 0,
      "offset": 0
    }
  }
}

Here is how I have defined my RootObject and Doc classes

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

public class Doc
{
    public Doc()
    {
        blog = new List<string>();
        headline = new NYTimes.Headline();
        keywords = new List<Keyword>();
        multimedia = new List<Multimedia>();
        byline = new Byline();
    }
    public string web_url { get; set; }
    public string snippet { get; set; }
    public string lead_paragraph { get; set; }
    public string @abstract { get; set; }
    public string print_page { get; set; }
    public List<string> blog { get; set; }
    public string source { get; set; }
    public Headline headline { get; set; }
    public List<Keyword> keywords { get; set; }
    public string pub_date { get; set; }
    public string document_type { get; set; }
    public string news_desK { get; set; }
    public string section_name { get; set; }
    public string subsection_name { get; set; }
    public Byline byline { get; set; }
    public string type_of_material { get; set; }
    public string _id { get; set; }
    public string word_count { get; set; }
    public string slideshow_credits { get; set; }
    public List<Multimedia> multimedia { get; set; }
}

And here is a small sample data set.

{
    "copyright": "Copyright (c) 2013 The New York Times Company.  All Rights Reserved.",
    "response": {
        "meta": {
            "hits": 7644
        },
        "docs": [{
                "web_url": "https://www.nytimes.com/slideshow/2016/01/01/nytnow/the-week-on-instagram.html",
                "snippet": "Photos posted on @nytimes during the last week of 2015 took followers from an island in Antarctica to Times Square, before the ball dropped....",
                "lead_paragraph": "Photos posted on @nytimes during the last week of 2015 took followers from an island in Antarctica to Times Square, before the ball dropped.",
                "abstract": null,
                "print_page": null,
                "blog": [],
                "source": "The New York Times",
                "multimedia": [{
                        "width": 190,
                        "url": "images/2016/01/01/nytnow/the-week-on-instagram-slide-8Y3Z/the-week-on-instagram-slide-8Y3Z-thumbWide.jpg",
                        "height": 126,
                        "subtype": "wide",
                        "legacy": {
                            "wide": "images/2016/01/01/nytnow/the-week-on-instagram-slide-8Y3Z/the-week-on-instagram-slide-8Y3Z-thumbWide.jpg",
                            "wideheight": "126",
                            "widewidth": "190"
                        },
                        "type": "image"
                    },
                    {
                        "width": 600,
                        "url": "images/2016/01/01/nytnow/the-week-on-instagram-slide-8Y3Z/the-week-on-instagram-slide-8Y3Z-articleLarge.jpg",
                        "height": 600,
                        "subtype": "xlarge",
                        "legacy": {
                            "xlargewidth": "600",
                            "xlarge": "images/2016/01/01/nytnow/the-week-on-instagram-slide-8Y3Z/the-week-on-instagram-slide-8Y3Z-articleLarge.jpg",
                            "xlargeheight": "600"
                        },
                        "type": "image"
                    },
                    {
                        "width": 75,
                        "url": "images/2016/01/01/nytnow/the-week-on-instagram-slide-8Y3Z/the-week-on-instagram-slide-8Y3Z-thumbStandard.jpg",
                        "height": 75,
                        "subtype": "thumbnail",
                        "legacy": {
                            "thumbnailheight": "75",
                            "thumbnail": "images/2016/01/01/nytnow/the-week-on-instagram-slide-8Y3Z/the-week-on-instagram-slide-8Y3Z-thumbStandard.jpg",
                            "thumbnailwidth": "75"
                        },
                        "type": "image"
                    }
                ],
                "headline": {
                    "main": "The Week on Instagram"
                },
                "keywords": [],
                "pub_date": "2016-01-01T00:00:00Z",
                "document_type": "multimedia",
                "news_desk": "NYT Now",
                "section_name": "NYT Now",
                "subsection_name": null,
                "byline": [],
                "type_of_material": "Slideshow",
                "_id": "5687cefb38f0d82225327003",
                "word_count": "24",
                "slideshow_credits": "Ben C. Solomon/The New York Times"
            },
            {
                "web_url": "https://wordplay.blogs.nytimes.com/2016/01/01/mass-master/",
                "snippet": "David Phillips provides our first Saturday challenge of 2016....",
                "lead_paragraph": null,
                "abstract": "David Phillips provides our first Saturday challenge of 2016.",
                "print_page": null,
                "blog": [],
                "source": "The New York Times",
                "multimedia": [{
                        "width": 190,
                        "url": "images/2016/01/01/crosswords/0101yalejpg/0101yalejpg-thumbWide.jpg",
                        "height": 126,
                        "subtype": "wide",
                        "legacy": {
                            "wide": "images/2016/01/01/crosswords/0101yalejpg/0101yalejpg-thumbWide.jpg",
                            "wideheight": "126",
                            "widewidth": "190"
                        },
                        "type": "image"
                    },
                    {
                        "width": 600,
                        "url": "images/2016/01/01/crosswords/0101yalejpg/0101yalejpg-articleLarge.jpg",
                        "height": 400,
                        "subtype": "xlarge",
                        "legacy": {
                            "xlargewidth": "600",
                            "xlarge": "images/2016/01/01/crosswords/0101yalejpg/0101yalejpg-articleLarge.jpg",
                            "xlargeheight": "400"
                        },
                        "type": "image"
                    },
                    {
                        "width": 75,
                        "url": "images/2016/01/01/crosswords/0101yalejpg/0101yalejpg-thumbStandard.jpg",
                        "height": 75,
                        "subtype": "thumbnail",
                        "legacy": {
                            "thumbnailheight": "75",
                            "thumbnail": "images/2016/01/01/crosswords/0101yalejpg/0101yalejpg-thumbStandard.jpg",
                            "thumbnailwidth": "75"
                        },
                        "type": "image"
                    }
                ],
                "headline": {
                    "main": "Mass Master",
                    "kicker": "Wordplay"
                },
                "keywords": [{
                    "rank": "1",
                    "name": "subject",
                    "value": "Crossword Puzzles"
                }],
                "pub_date": "2016-01-01T22:00:56Z",
                "document_type": "blogpost",
                "news_desk": "Business",
                "section_name": "Crosswords & Games",
                "subsection_name": null,
                "byline": {
                    "person": [{
                        "organization": "",
                        "role": "reported",
                        "firstname": "Deb",
                        "rank": 1,
                        "lastname": "AMLEN"
                    }],
                    "original": "By DEB AMLEN"
                },
                "type_of_material": "Blog",
                "_id": "56873d8d38f0d82225326f90",
                "word_count": "618",
                "slideshow_credits": null
            },
            {
                "web_url": "https://krugman.blogs.nytimes.com/2016/01/01/friday-night-music-more-wild-reeds/",
                "snippet": "Harmony to soothe the soul....",
                "lead_paragraph": null,
                "abstract": "Harmony to soothe the soul.",
                "print_page": null,
                "blog": [],
                "source": "The New York Times",
                "multimedia": [],
                "headline": {
                    "main": "Friday Night Music: More Wild Reeds",
                    "kicker": "Paul Krugman"
                },
                "keywords": [],
                "pub_date": "2016-01-01T21:17:09Z",
                "document_type": "blogpost",
                "news_desk": "OpEd",
                "section_name": "Opinion",
                "subsection_name": null,
                "byline": {
                    "person": [{
                        "organization": "",
                        "role": "reported",
                        "firstname": "Paul",
                        "rank": 1,
                        "lastname": "KRUGMAN"
                    }],
                    "original": "By PAUL KRUGMAN"
                },
                "type_of_material": "Blog",
                "_id": "5687337938f0d82225326f8b",
                "word_count": "13",
                "slideshow_credits": null
            }
        ]
    }
}

I have attempted to decorate the classes in question with the [JsonArray(AllowNullItems = true)] attribute but to no avail. The only thing that results in no exceptions, is when I change the variables explicitly declared as class objects to object, but this isn't exactly ideal.

Any help would be greatly appreciated. I just can't seem to get this deserialization working.

The call to deserialize the json.

public void GetThisMonthsArticles()
{
    string endpoint = string.Format(Endpoint, new object[] { DateTime.Now.Year, DateTime.Now.Month, Resources.APIKeys.NYTimesAPI });
    HttpClient client = HttpPersistentClient.GetHttpClient(endpoint);

    WebClient wc = new WebClient();
    string json = wc.DownloadString(endpoint);

    RootObject = JsonConvert.DeserializeObject<RootObject>(json);
}
Ingenioushax
  • 718
  • 5
  • 20
  • 1
    it says what it says: in your json output `keywords` property is an array (`"keywords": [],`), and your your model expects an object (`public Keywords keywords`). – DolceVita Jun 14 '17 at 19:56
  • Can you show the code where you're calling DeserializeObject ? – mjw Jun 14 '17 at 20:04
  • I have attempted quite a few variations for the keywords property type in the following ways: `List`, `IList`. `Keyword`, `Dictionary>`, `object`, `List`. The only one that was remotely successful was `List`. – Ingenioushax Jun 14 '17 at 20:15
  • Added the call to DeserializeObject<> – Ingenioushax Jun 14 '17 at 20:20
  • 2
    You have two JSON samples in your question. The first one shows `keywords` as being a single object; the second one shows it as being an *array* of objects. Which one is the one you are trying to deserialize? Or is it both of them? If both, you will need a JsonConverter to solve this. See [How to handle both a single item and an array for the same property using JSON.net](https://stackoverflow.com/q/18994685/10263) – Brian Rogers Jun 14 '17 at 20:46
  • I believe you're right @BrianRogers. I implemented the converter and no longer receive the error. Now I just get a StackOverflowException. I think I can handle that though. Much appreciated. Keywords I believe can be either an array or values, or just an object. The NYTimes API page gives both of those examples as the structure of the JSON. The second one is 2 results of about 1400, and the first one is the structure provided by NYTimes. – Ingenioushax Jun 14 '17 at 22:24

1 Answers1

0

Maybe you made some mistake in your classes.

You can try to use this tool: https://jsonutils.com/

It will generate all classes for you. Remember to select JsonProperty in dropdown.

Then you can just use:

var object = JsonConvert.DeserializeObject<RootObject>(json);

Finally, you can access your data by LINQ query on your object to get what you need. I hope it will be useful for you.

Jakub Nowacki
  • 121
  • 2
  • 11
  • I used the `jsonutils` and while the output is much nicer, it produces about the same results as json2csharp. I also get the same error message. – Ingenioushax Jun 14 '17 at 20:17