skip to Main Content

I am pulling down JSON data from a website and some of the fields are optional, meaning they do not always exist. When I try to query them, my program breaks. I believe I am checking for nulls incorrectly.

using (var httpClient = new HttpClient())
{
    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "myToken");
    httpClient.BaseAddress = new Uri("https://app.website.com/data/");
                    
    HttpResponseMessage response = httpClient.GetAsync("getData.json?thing=abc").Result;
    response.EnsureSuccessStatusCode();

    var result = JsonConvert.DeserializeObject<JToken>response.Content.ReadAsStringAsync().Result);

    if (result.HasValues)
    {
        var count = result.SelectTokens("results.main[*].item").Count();

        if (count > 0)
        {
            var total = result.SelectTokens("results.main[*].item.total").First() != null ? result.SelectTokens("results.main[*].item.total").First().ToString() : string.Empty;
        }
    }
}

It breaks when I try to query for total even though I’m checking for nulls. Since it’s not always a guarantee that total is returned, how can I check for it first?

The JSON array looks like this. Sometimes total exists and other times it doesn’t.

{
    "results": {
        "copyright": "Copyright (c)",
        "main": [{
            "item": {
                "name": fruit,
                "date": 1900-01-01,
                "total": 123456
            }
        }]
    }
}

2

Answers


  1. Your immediate problem is that you are trying to get the value of the first "total" by calling SelectTokens("...").First(). The extension method Enumerable.First() will throw an exception if the enumerable is empty and so is not appropriate to use when querying for optional values. Instead, you could use FirstOrDefault() or SingleOrDefault(). (Enumerable.Count() should be avoided when you only need to check if an enumerable has content, as it will enumerate the entire sequence.)

    Thus your code can be simplified to:

    var result = JsonConvert.DeserializeObject<JToken>response.Content.ReadAsStringAsync().Result);
    
    var total = (string)result.SelectTokens("results.main[*].item.total").SingleOrDefault() ?? string.Empty;
    if (!string.IsNullOrEmpty(total))
    {
        // Process the total somehow.
    }
    

    That being said, since you are expecting only one item in the results.main[*] array, you could just use SelectToken() instead of SelectTokens():

    var total = (string)result.SelectToken("results.main[0].item.total") ?? string.Empty;
    

    Alternatively, if you the results.main[*] might have multiple items and only some have totals, you could use the JSONPath conditional operator to select them:

    var items = result.SelectTokens("results.main[?(@.item.total)].item");
    foreach (var item in items)
    {
        var name = (string)item["name"];
        var total = (decimal)item["total"];
        Console.WriteLine("name = {0}, total = {1}", name, total);
    }
    

    Notes:

    • Calling SelectToken() or SelectTokens() does incur a performance cost, so I would recommend against making the same query more than once just so that to do everything in one (long) line of code.

    • As explained in Avoiding Deadlock with HttpClient, httpClient.GetAsync(url).Result; can sometimes result in deadlocks, so you might want to rewrite your method to be asynchronous.

    Demo fiddle here.

    Login or Signup to reply.
  2. your json is not valid. But if you know how to fix it, you can get your data in one line of code.

    int? total=JObject.Parse(json)
                      .SelectToken("results.main")
                      ?.Select(j => (int?) j.SelectToken("item.total"))
                      .FirstOrDefault()
    

    It will be working without any exception if even the list "main" is not exist.

    but if you need to know if an array exist and has at least one item

        bool? isArrayExist = JObject.Parse(json)
        .SelectToken("results.main")
        ?.Any();
    

    P.S. All explanations you can find @dbc answer

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search