JSON (JavaScript Object Notation) has been adopted rather quickly as an easy way to transfer data between web applications. As a result, its clear benefit is its ability to serialize and deserialize easily. (As an aside, I’m not clear why XML wasn’t good enough, but I’m sure that’s available somwehere.) One sticking point I’ve found for JSON, especially when it comes to trying to read the content of the data with my eyes to debug things, is the “array of objects” part. Similarly, JSON, or at least applications that use JSON, use arrays more than are necessary, and can lead to some confusion when trying to work with them.
Suppose you have a specification for “point of contact” and in there is an attribute called “email addresses”. As you might expect from the attribute name, this should handle multiple addresses. Of course, the simplest method would be to have “emails” be an array: [“kevin@udm-tech.com”,”kcrusch@gmail.com”….]. However, each email
address is more likely to wind up as an object with address and description. (Also, to drive home my general gripe, I’ll make these objects part of a list):
“firstName”:”Kevin”,
“lastName”:”Rusch”,
“email”:
{
“type”:”work”,
“address”:”kevin@udm-tech.com”
},
“title”:”consultant”
}]
which then means you wind up with an array of objects:
“firstName”:”Kevin”,
“lastName”:”Rusch”,
“emails”:[ {“type”:”work”,”address”:”kevin@udm-tech.com”},{“type”:”home”,”address”:”kcrusch@gmail.com”}],
“title”:”consultant”
}]
When I’m writing code in C# to work with this, here’s what I end up doing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class TenantRoot { public List<TenantItem> items { get; set; } } public class TenantItem { public string firstName {get; set;} public string lastName {get; set;} public List<EmailAddress> emails; public string title {get; set;} } public class EmailAddress { public string type {get; set;} public string address {get; set;} } |
I’m still okay at this point. Where I personally get thrown off is when the email attribute is supposed to be an object that is itself a list of objects. To wit:
“firstName”:”Kevin”,
“lastName”:”Rusch”,
“email”: { [ {“type”:”work”,”address”:”kevin@udm-tech.com”},{“type”:”home”,”address”:”kcrusch@gmail.com”}] },
“title”:”cranky consultant”
}]
I don’t know if this is for a specific purpose, or just a quirk of the serializers that I’ve come across, but it seems silly to me for two reasons. First, the sea of brackets and braces get very difficult to determine, and second, because this is what my C# code ends up looking like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public class TenantRoot { public List<TenantItem> items { get; set; } public TenantRoot() { items = new List<TenantItem>(); } } public class TenantItem { public string firstName {get; set;} public string lastName {get; set;} public EmailListRoot emails; public string title {get; set;} public TenantItem() } public class EmailListRoot { public List<EmailAddress> items; public EmailListRoot() { items = new List<EmailAddress>(); } } public class EmailAddress { public string type {get; set;} public string address {get; set;} } |
So that’s inconvenient, but at least it’s strongly typed, and when I get a JSON response from the app on the other end of the wire, I can deserialize it and have the information I need.
But wait! There’s more!
So I have been dealing with a third-party provider, Capsule, who will send you all the JSON you can handle, and when they have a data field that can have more than one item, they turn it into an array of those items if there are more than one in practice, but will only send a single item if there is only one. This changes dynamically, on a per-object basis, which strikes me as very poor form. Just to make sure I’m being clear here. If there is a contact who happens to only have one email address, this is what you’ll see when you request the particulars
on that contact via their JSON API:
“firstName”:”Kevin”,
“lastName”:”Rusch”,
“email”:
{
“type”:”work”,
“address”:”kevin@udm-tech.com”
},
“title”:”consultant”
}]
but if this contact gets a second email address added, and you make the same call, you’ll get:
“firstName”:”Kevin”,
“lastName”:”Rusch”,
“email”:
[
{
“type”:”work”,
“address”:”kevin@udm-tech.com”
},
{
“type”:”personal”,
“address”:”kcrusch@gmail.com”
}
]
“title”:”consultant”
}]
That’s right – the definition of the data changes depending on what the data is. That’s a great idea for a grad school paper, but in practice, I consider that poor form. It’s also not documented, because telling your developers about that takes all the fun out.
Maybe there are some JSON deserializers that are able to handle this dynamically, but the one that C# uses (Newtonsoft) doesn’t, because this is fundamentally changing the datatype.
So, rather than try to be too elegant about it, I wrote a little converter to handle it. It’s clunky, but I couldn’t think of anything better, and I was able to make it work.
The general idea is that the email address (in this case) will *always* be an array of objects, and if there is only 1 email address, I’ll stick square brackets into the raw JSON to turn that email address object into a single-element array.
So here’s the class definition first:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public class TenantRoot { public List<TenantItem> items { get; set; } public TenantRoot() { items = new List<TenantItem>(); } } public class TenantItem { public string firstName {get; set;} public string lastName {get; set;} public EmailListRoot emails; public string title {get; set;} public TenantItem() } public class EmailListRoot { public List<EmailAddress> items; public EmailListRoot() { items = new List<EmailAddress>(); } } public class EmailAddress { public string type {get; set;} public string address {get; set;} } |
And here’s the code I use to get the JSON from the partner:
1 2 3 4 5 6 | RestRequest myRequest = new RestRequest("party/" + sOrganizationID, Method.GET); myRequest.AddHeader("Accept", "application/json"); RestResponse capsuleResponse = (RestResponse)myClient.Execute(myRequest); responseContent = capsuleResponse.Content; // raw content as string responseContent = MakeJSONAnArray(responseContent, "email"); myOrganization = Newtonsoft.Json.JsonConvert.DeserializeObject<OrganizationContainer>(ResponseContent); |
And finally, when I get the raw JSON back, I use the following code to convert the raw JSON into the proper array-of-objects format that the deserializer needs.
It’s not very elegant, but it does work, and it’s flexible. You’ll see that I keep a stack of open/close curly braces so that we know we’re putting the square bracket in the right place.
This function looks through the raw JSON for the keyword (e.g. “email”) and its delimiters, as follows:
So if we’re looking for email, we’d get a hit on:
And replace it with:
Then it goes through the remainder of the string looking for { or }. It counts up the open braces it finds, then counts back down them until it finds the correct closing brace.
When the correct brace is found, it just inserts a ] into the string and returns the fixed string.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public static string MakeJSONItemAnArray(string sInputString, string sToken) { try { int iStartIndex; int iEndIndex; int iTemp; int iMiddleLength; string sFindTag = @"""" + sToken + @""":{"; string sReplaceTag = @"""" + sToken + @""":[{"; while (sInputString.Contains(sFindTag)) { iStartIndex = sInputString.IndexOf(sFindTag); iEndIndex = sInputString.IndexOf('}', iStartIndex); if (iStartIndex > 0 && iEndIndex > 0) { iStartIndex = sInputString.IndexOf(sFindTag); iTemp = cCapsule.FindMatchingDelimiter(sInputString, sInputString.IndexOf(sFindTag) + sFindTag.Length, '{', '}'); iMiddleLength = iTemp - (iStartIndex + sFindTag.Length); string sStart = sInputString.Substring(0, iStartIndex); string sMiddle = sReplaceTag + sInputString.Substring((iStartIndex + sFindTag.Length), iMiddleLength + 1) + ']'; // we added 2 characters to the lengths of each of those substrings, so we need to adjust for those. string sEnd = sInputString.Substring((sStart.Length + sMiddle.Length) - 2); sInputString = sStart + sMiddle + sEnd; } } } catch (Exception ex) { ; } return sInputString; } |
Hope this comes in handy!