Grimdari Grimdari - 5 months ago 22
JSON Question

Append to JSON array within an JSON array ColdFusion

This is a follow up question to: Append to JSON array with ColdFusion, taking Null values into consideration?

That question was answered yesterday and worked perfectly (Thank you Kevin B. and Leigh!). However, the application I am pulling my JSON data from threw me a curve ball this morning. Sometimes, depending on the data I am requesting, it returns the entire JSON as an array like this:

[
{
"loginHosts": [
"server1.example.com"
],
"sudoHosts": [
"server1.example.com"
],
"CPG": [
"my_group"
],
"mail": "myuser@example.com",
"loginShell": "/bin/bash"
}
]


I don't know why that application does this. If I knew this was a possibility I would have added that information to my previous question, my apologies.

My attempts to find a solution lead me here first: Using JSON Data with Coldfusion . Looping over the JSON array as a collection seemed to work, but only if none of the array values were Null. I thought using this code, as in the previous question, would work if I used it for all the JSON fields:

<cfif NOT structKeyExists(myStruct, 'sudoHosts') OR NOT isArray(myStruct.sudoHosts)>
<cfset myStruct.sudoHosts = []>
</cfif>


This was not the case. I continually get:


Error: Can't cast Complex Object Type Array to String


Looking through the debug output, Lucee did throw this out:
string Use Built-In-Function "serialize(Array):String" to create a String from Array
. I did more digging and found this article: Railo tip: store complex data by using serialize(data). Sadly, Null values have struck again. Also, my understanding is
serialize()
is similar to
evaluate()
, and not recommended.

I will continue looking for a solution but any help is, as always, greatly appreciated!

-- EDIT --

I came across this thread: ColdFusion JSON object vs array of objects. I noticed the JSON in the question is an ARRAY
[]
, and I applied the answer to my code, but am still running into the Null problem. I guess I don't know how to check for nested Null values. :(

Answer

Take it one step at a time.

Ideally you should determine why the response differs. Since you say those differences usually correspond to something different in your request, that strongly suggests you may be overlooking (or possibly misunderstanding) something in the remote API. I would recommend re-reviewing the API to identify that "something", in order to figure out the right approach. Otherwise, the code will quickly become unmanageable and inefficient as you continue to tweak it to handle each "new" situation.

If for some reason the API truly is returning different results without a valid reason, the best you can do is to code according to what you expect and fail gracefully when you receive something else. Start by listing the expected possibilities:

  1. Response is a single structure containing certain keys OR
  2. Response is an Array of structures containing certain keys

Based on the above, you can use the IsArray and IsStruct functions to determine the format of the response, and handle it accordingly. First examine the deserialized object. If it is an array, extract the structure in the first element (Note, I am assuming the array only contains a single element, as in the example. If it can contain multiple elements, you will need additional handling).

<cfset data = deserializeJson(originalJSON)>
....
<!--- Extract structure from first element of array --->
<cfif IsArray(data) && arrayLen(data)>
    <cfset data = data[1]>
</cfif>

Next verify you are now working with a structure, containing the expected key(s). If so, go ahead with your usual processing. Otherwise, something unexpected happened and the code should perform the appropriate error handling.

<!--- Verify object is a structure and contains expected key(s) --->
<cfif IsStruct(data) && structKeyExists(data, "loginHosts")>
    ... process data as usual
<cfelse>
    ... data is not in expected format, do  error handling here
</cfif>

The above is a very quick and dirty example, but should demonstrate the basic idea. As long as you are certain you are using the API correctly, all you can do is code for the expected and fail gracefully when something different happens.