0

I'm very new to c#, so I apologize if this doesn't make any sense!

Using a C# console application, I'm having a hard time parsing a detailed json response and assigning json values to variables. I thought I had everything working by deserializing the json to a string until about 200 iterations in (I'll be geocoding over a million addresses), I received a response with an empty result array that crashed my application. Now I'm trying a new approach using JObject, JProperty, and JToken, but not having much luck.

My json example is is follows ..

{
  "input": {
    "address_components": {
      "number": "123",
      "predirectional": "E",
      "street": "Main",
      "suffix": "St",
      "formatted_street": "E Main St",
      "city": "Mesa",
      "state": "AZ",
      "zip": "85209",
      "country": "US"
    },
    "formatted_address": "123 E Main St, Mesa, AZ 85209"
  },
  "results": [
    {
      "address_components": {
        "number": "123",
        "predirectional": "E",
        "street": "Main",
        "suffix": "St",
        "formatted_street": "E Main Ave",
        "city": "Mesa",
        "county": "Maricopa County",
        "state": "AZ",
        "zip": "85209",
        "country": "US"
      },
      "formatted_address": "123 E Main St, Mesa, AZ 85209",
      "location": {
        "lat": 33.123456,
        "lng": -111.123456
      },
      "accuracy": 1,
      "accuracy_type": "range_interpolation",
      "source": "TIGER\/Line\u00ae dataset from the US Census Bureau",
      "fields": {
        "congressional_district": {
          "name": "Congressional District 5",
          "district_number": 5,
          "congress_number": "114th",
          "congress_years": "2015-2017"
        },
        "state_legislative_districts": {
          "senate": {
            "name": "State Senate District 16",
            "district_number": "16"
          },
          "house": {
            "name": "State House District 16",
            "district_number": "16"
          }
        },
        "school_districts": {
          "unified": {
            "name": "Gilbert Unified District",
            "lea_code": "0403400",
            "grade_low": "PK",
            "grade_high": "12"
          }
        },
        "timezone": {
          "name": "MST",
          "utc_offset": -7,
          "observes_dst": false
        }
      }
    },
    {
      "address_components": {
        "number": "123",
        "predirectional": "E",
        "street": "Main",
        "suffix": "St",
        "formatted_street": "E Main St",
        "city": "Mesa",
        "county": "Maricopa County",
        "state": "AZ",
        "zip": "85209",
        "country": "US"
      },
      "formatted_address": "123 E Main St, Mesa, AZ 85209",
      "location": {
        "lat": 33.123456,
        "lng": -111.123456
      },
      "accuracy": 0.8,
      "accuracy_type": "range_interpolation",
      "source": "TIGER\/Line\u00ae dataset from the US Census Bureau",
      "fields": {
        "congressional_district": {
          "name": "Congressional District 5",
          "district_number": 5,
          "congress_number": "114th",
          "congress_years": "2015-2017"
        },
        "state_legislative_districts": {
          "senate": {
            "name": "State Senate District 16",
            "district_number": "16"
          },
          "house": {
            "name": "State House District 16",
            "district_number": "16"
          }
        },
        "school_districts": {
          "unified": {
            "name": "Gilbert Unified District",
            "lea_code": "0403400",
            "grade_low": "PK",
            "grade_high": "12"
          }
        },
        "timezone": {
          "name": "MST",
          "utc_offset": -7,
          "observes_dst": false
        }
      }
    }
  ]
}

The json that broke my original code ..

{
  "input": {
    "address_components": {
      "number": "123",
      "predirectional": "E",
      "street": "Main",
      "suffix": "St",
      "formatted_street": "E Main St",
      "city": "Mesa",
      "state": "AZ",
      "zip": "85209",
      "country": "US"
    },
    "formatted_address": "123 E Main St, Mesa, AZ 85209"
  },
  "results": []
}

The original code ..

Uri uri = new Uri("https://api.geocod.io/v1/geocode?q=" + geocodioAddress + "&fields=cd,stateleg,school,timezone&api_key=" + app_key);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Method = WebRequestMethods.Http.Get;
request.Accept = "application/json";

HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());
string output = reader.ReadToEnd();
response.Close();

dynamic array = JsonConvert.DeserializeObject(output);

if (array.results[0] != null)
{
    // cont.
}

The error msg was "Index was out of range. Must be non-negative and less than the size of the collection." The error occurs at "if (array.results[0] != null)".


Now I'm sure this isn't the best approach anyways, so I thought I'd try something new (found here: C# Parsing JSON array of objects) ..

Uri uri = new Uri("https://api.geocod.io/v1/geocode?q=" + geocodioAddress + "&fields=cd,stateleg,school,timezone&api_key=" + app_key);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Method = WebRequestMethods.Http.Get;
request.Accept = "application/json";

HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());
string json = reader.ReadToEnd();
response.Close();

var resultObjects = AllChildren(JObject.Parse(json))
    .First(c => c.Type == JTokenType.Array && c.Path.Contains("results"))
    .Children<JObject>();

foreach (JObject result in resultObjects)
{
    foreach (JProperty property in result.Properties())
    {
        JToken _county = property.Value.SelectToken("county");
        string county = Convert.ToString(_county);

        // cont.
    }
}

This looked really promising, except for three things..

  1. I don't want to parse results[1]. You'll notice in the json response, that the second results instance has a lower accuracy score. And when I don't change the lat/lng values to hide my personal address, these two instances are different with the 2nd being much less accurate.

  2. While I successfully got the value for the county above, I can't get a response for "formatted_address", as well as the value resets each time through the foreach loop.

  3. In the "fields" section, there are multiple objects with the same name. For example..

    JToken _county = property.Value.SelectToken("name");

How do I select which "name" I'm looking for? school district, timezone, congressional district, etc..

Again, I'm sorry for such a long post. I've been working on this all week, and just when I thought I had it figured out, one stupid address has to return no results and break everything!! I really appreciate the help of people much smarter than me ... the downside of working from home, no other brains to pick :)

Community
  • 1
  • 1
Tim Halbert
  • 477
  • 1
  • 4
  • 9
  • Your first bit of code is good, but you should try to deserialize to a class using `JsonConvert.DeserializeObject(string)`. This way, you can access the values statically. You can check if there is at least one item in the array by doing `array != null && array.Length > 0`. Hope this helps! – Philippe Paré Mar 03 '16 at 04:41
  • Hi Tim. This is Mathias from geocod.io. One of our customers shared this example that he wrote to deserialize the response. Feel free to check it out here: https://gist.github.com/btompkins/8722291 – CodeMonkey Mar 03 '16 at 14:43

1 Answers1

1

If you look at the data that broke your code:

{
 {
   "input": {
   ..
   },
  "formatted_address": "123 E Main St, Mesa, AZ 85209"
  },
 "results": []  
}

You have results defined as an empty array. In other words it contains zero elements. Thus trying to access the first element (at index 0) in this array results in the error that you are getting.

Instead of the test that you are doing:

if (array.results[0] != null)
{
   // cont.
}

you should do:

if (array.Length != 0)
{
   // cont.
}

this is because the 'results' object exists, but it is empty (length is zero).

Jens Meinecke
  • 2,904
  • 17
  • 20