Convert coldfusion json to struct - json

I have a JSON variable output from a jQuery application as so:
[{"id":1},
{"id":197,"children":[{"id":198},{"id":199},{"id":200}]},
{"id":2,"children":[{"id":3},{"id":4},{"id":143},{"id":6},{"id":5},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12}]},
{"id":15,"children":[{"id":17},{"id":190},{"id":191},{"id":131},{"id":16},{"id":142},{"id":124}]},
{"id":149,"children":[{"id":150},{"id":160},{"id":161},{"id":184}]},
{"id":20,"children":[{"id":132},{"id":127},{"id":152},{"id":107},{"id":108},{"id":109},{"id":110},{"id":125},{"id":128},{"id":130},{"id":129},{"id":112}]},
{"id":162,"children":[{"id":163},{"id":164},{"id":165},{"id":166},{"id":186},{"id":187}]},
{"id":167,"children":[{"id":173},{"id":168},{"id":170},{"id":169},{"id":192},{"id":194}]},
{"id":174,"children":[{"id":176},{"id":175},{"id":188}]},
{"id":153,"children":[{"id":178},{"id":179},{"id":195},{"id":154},{"id":157}]},
{"id":180,"children":[{"id":181},{"id":182},{"id":183},{"id":189}]},
{"id":122},
{"id":21},
{"id":13},
{"id":92},
{"id":22},
{"id":113},
{"id":114}]
I need to convert this variable to a struct and then loop the struct by the first id parameter to find the children for each id (if there is one). I have tried to convert it as a structure like so but I am getting an error:
<cfset jsonarray = deserializeJson(output)>
<cfloop collection="#jsonarray#" index="id">
<cfdump var="#jsonarray[id]#">
</cfloop>
It does not recognize the variable as a structure:
Invalid collection [{id={1}}, {children={[{id={198}}, {id={199}}, {id={200}}]},id={197}}, {children={[{id={3}}, {id={143}}, {id={4}}, {id={6}}, {id={5}}, {id={7}}, {id={8}}, {id={9}}, {id={10}}, {id={11}}, {id={12}}]},id={2}}, {children={[{id={17}}, {id={190}}, {id={191}}, {id={131}}, {id={16}}, {id={142}}, {id={124}}]},id={15}}, {children={[{id={150}}, {id={160}}, {id={161}}, {id={184}}]},id={149}}, {children={[{id={132}}, {id={127}}, {id={152}}, {id={107}}, {id={108}}, {id={109}}, {id={110}}, {id={125}}, {id={128}}, {id={130}}, {id={129}}, {id={112}}]},id={20}}, {children={[{id={163}}, {id={164}}, {id={165}}, {id={166}}, {id={186}}, {id={187}}]},id={162}}, {children={[{id={173}}, {id={168}}, {id={170}}, {id={169}}, {id={192}}, {id={194}}]},id={167}}, {children={[{id={176}}, {id={175}}, {id={188}}]},id={174}}, {children={[{id={178}}, {id={179}}, {id={195}}, {id={154}}, {id={157}}]},id={153}}, {children={[{id={181}}, {id={182}}, {id={183}}, {id={189}}]},id={180}}, {id={122}}, {id={21}}, {id={13}}, {id={92}}, {id={22}}, {id={113.... Must be a valid structure or COM object.

In JSON, the [] denotes an array and {} a structure (or object). So your input is actually an array of structures. You need to use an array loop, not a collection loop:
<cfset arrayOfStructs = deserializeJson(output)>
<cfloop array="#arrayOfStructs#" index="parent">
<cfset parentID = parent.id />
...
</cfloop>
children is also an array of structures. Inside the outer loop, check for the existence of that key. If found, loop through the child array and do something with each of the id's:
<cfif structKeyExists(parent, "children")>
<cfloop array="#parent.children#" index="child">
...
</cfloop>
</cfif>

A tidy cfscript version. :)
<cfscript>
structObj = deserializeJson(jsonString);
for(i = 1; i LTE ArrayLen(structObj); i++){
WriteOutput("parent id : " & structObj[i].id & "<br>");
if(StructKeyExists(structObj[i], "children")){
for(j = 1; j LTE ArrayLen(structObj[i].children); j++){
WriteOutput(" -children id : " & structObj[i].children[j].id & "<br>");
}
}
}
</cfscript>

I created an Angular 1.4 ColdFusion 9 JSON Normalizer here
var myURL = 'myCfc.cfc?method=getItemsFromDb';
var app = angular.module('angularOutput',[]);
app.controller("sectionController", function($scope, $http) {
$http.get(myURL).
success(function(data, status, headers, config) {
var log = [];
var output = '';
angular.forEach(data.DATA, function(value, key) {
this.push(output +='{"value": ');
this.push(output += '"'+value[0]+'"');
this.push(output +=',"text":');
this.push(output += '"'+value[1]+'"');
this.push(output +='}');
this.push(output +=',');
}, log);
output = output.replace(/,\s*$/, "");/*had to remove the final comma */
output = '['+output+']'; /*had to add the [] to corectally form the output*/
$scope.sections = angular.fromJson(output);
}).
error(function(data, status, headers, config) {
console.log(data);
});
});

Related

U-SQL JsonTuple - How to access specific fields in JSON array

I'm extracting AVRO data which has a JSON field that I need to get values from. The JSON has an array, and I don't know what order the different elements of the array may appear in. How can I target specific node/values?
For example, Filters[0] could be Category one time, but could be AddressType another time.
I'm extracting AVRO data - i.e.
#rs =
EXTRACT date DateTime,
Body byte[]
FROM #input_file
USING new Microsoft.Analytics.Samples.Formats.ApacheAvro.AvroExtractor(#"
...
The Body is JSON that can look like this (but Category is not always Filter[0]. This is a small example; there are 7 different types of "Field"s):
{
""TimeStamp"": ""2019-02-19T15:00:29.1067771-05:00"",
""Filters"": [{
""Operator"": ""eq"",
""Field"": ""Category"",
""Value"": ""Sale""
}, {
""Operator"": ""eq"",
""Field"": ""AddressType"",
""Value"": ""Home""
}
]
}
My U-SQL looks like this, which of course does not always work.
#keyvalues =
SELECT JsonFunctions.JsonTuple(Encoding.UTF8.GetString(Body),
"TimeStamp",
"$.Filters[?(#.Field == 'Category')].Value",
"$.Filters[?(#.Field == 'AddressType')].Value"
) AS message
FROM #rs;
#results =
SELECT
message["TimeStamp"] AS TimeStamp,
message["Filters[0].Value"] AS Category,
message["Filters[1].Value"] AS AddressType
FROM #keyvalues;
Although this does not actually answer my question, as a workaround, I modified the Microsoft 'sample' JsonFunctions.JsonTuple method to be able to specify my own key name for extracted values:
/// Added - Prefix a path expression with a specified key. Use key~$e in the expression.
/// eg:
/// JsonTuple(json, "myId~id", "myName~name") -> field names MAP{ {myId, 1 }, {myName, Ed } }
The modified code:
private static IEnumerable<KeyValuePair<string, T>> ApplyPath<T>(JToken root, string path)
{
var keySeparatorPos = path.IndexOf("~");
string key = null;
var searchPath = path;
if (keySeparatorPos > 0) // =0?if just a leading "=", i.e. no key provided, then don't parse out a key.
{
key = path.Substring(0, keySeparatorPos).Trim();
searchPath = path.Substring(keySeparatorPos + 1);
}
// Children
var children = SelectChildren<T>(root, searchPath);
foreach (var token in children)
{
// Token => T
var value = (T)JsonFunctions.ConvertToken(token, typeof(T));
// Tuple(path, value)
yield return new KeyValuePair<string, T>(key ?? token.Path, value);
}
}
For example, I can access vales and name them
#keyvalues =
SELECT JsonFunctions.JsonTuple(Encoding.UTF8.GetString(Body),
"TimeStamp",
"EventName",
"Plan~ $.UrlParams.plan",
"Category~ $.Filters[?(#.Field == 'Category')].Value",
"AddressType~ $.Filters[?(#.Field == 'AddressType')].Value"
) AS message
FROM #rs;
#results =
SELECT
message["TimeStamp"] AS TimeStamp,
message["EventName"] AS EventName,
message["Plan"] AS Plan,
message["Category"] AS Category,
message["AddressType"] AS AddressType
FROM #keyvalues;
(I've not tested to see what would happen if the same Field appears multiple times in the array; That won't happen in my case)

Coldfusion Json from structure

I'm using this code
<cfset user_data = StructNew() />
<cfset user_data[["field_a"]["und"]["tid"] = '123' />
<cfset json = SerializeJSON(user_data)>
It generates this json
{"und":{"tid":123}}
I would like to have the json look like this instead
{"und":[{"tid":123}]}
How do I have to change the struct?
Use the following cfscript:
user_data = { 'und' = [ { 'tid' = '123' } ] };
json = serializeJSON(user_data);
you're not putting the key/value pair in an array as needed by your request.

how do I change the label for each object in SerializeJSON

I'm trying to use ColdFusion's SerializeJSON() to return JSON data.
So far I have done this:
<cfset rows = [] />
<cfloop query="rsProducts">
<!--- Create a row struct. --->
<cfset row = {} />
<!--- Add each column to our struct. --->
<cfloop
index="column"
list="#rsProducts.columnList#"
delimiters=",">
<cfset row[ column ] = rsProducts[ column ][ rsProducts.currentRow ] />
</cfloop>
<!--- Append the row struct to the row array. --->
<cfset arrayAppend( rows, row ) />
</cfloop>
<cfreturn SerializeJSON(rows, true)>
This works fine and produces JSON like this:
[Object {PRICE: 89.99, PRODUCTSTATUS: 1, COUNTRY: US}, Object {PRICE: 110.50, PRODUCTSTATUS: 4, COUNTRY: UK}, Object {PRICE: 41.20, PRODUCTSTATUS: 1, COUNTRY: IN}]
However instead of a label of "Object" for each item, I'd like it to be "ProductItem" instead. It just makes it easier for me to deal with in jQuery later on.
How could I have it so it labels each object in JSON as "ProductItem"?
You can loop over the data in this manner easily.
.success(function(data) {
var ProductItem = JSON.parse(data);
$.each(ProductItem,function(key,value){
console.log(value.PRICE +" "+ value.PRODUCTSTATUS + " " + value.COUNTRY);
});

ColdFusion: how to check whether JSON property is null?

Let's say I POST this simple JSON payload:
{"foo":null}
On the ColdFusion server, how do I check if the 'foo' property is null?
IsDefined won't work because it will be false for null values. IsNull won't work because IsNull will be true for not just null values, but also for missing properties.
<cfset json = DeserializeJSON(GetHttpRequestData().content) />
<cfdump var="#IsDefined("json.foo")#" /> <!--- false --->
<cfdump var="#IsNull(json.foo)#" /> <!--- true --->
<cfdump var="#IsNull(json.bar)#" /> <!--- true --->
My mistake, I thought null in JSON would be deserialized to empty string, but it's not true.
null in JSON is translated to be struct with key foo but undefined in CF10. (not sure about older CF version)
Therefore, a true isStructValueNull() can be written like this:
function isStructValueNull(struct, key) {
return listFind(structKeyList(struct), key)
&& !structKeyExists(struct, key);
}
json = deserializeJSON('{"foo":null,"bar":123}');
writeDump(isStructValueNull(json, "foo")); // yes
writeDump(isStructValueNull(json, "bar")); // no
or you can loop through json and use structKeyExists(), if it's false, it's null.
function structNullKeyList(struct) {
var nulls = "";
for (var key in struct)
if (!structKeyExists(struct, key))
nulls = listAppend(nulls, key);
return nulls;
}
writeDump(structNullKeyList(json)); // 'foo'
How are you posting this data? What do you get when you dump just the variable "json"?
If you're POSTing data to ColdFusion, each parameter should get converted to a FORM scoped variable. Check the headers of your HTTP POST to see how the data is being sent.
If they're being sent as individual parameters, you should be able to get check <cfif structKeyExists(form, "foo")> and then check to see if the value of form.foo is an empty string. (ColdFusion translates a NULL value to an empty string.)
I use Raillo.
Railo 4.1.3.005 Error (expression)
Message the value from key [FOO] is NULL, which is the same as not existing in CFML.
But we can use Full Null suport. Where it can be properly examined, as you want.
<cfscript>
objectValues = { 'one' : 1 , 'two' : 2 , 'three' : JavaCast( "null", 0 ) , 'four' : null };
dump(objectValues);
// Known existing attribute
dump('three');
dump( isDefined('objectValues.three') );
dump( isNull(objectValues.three) );
dump( StructKeyExists(objectValues,'three') );
// Known Railo Null value
dump('four');
dump( isDefined('objectValues.four') );
dump( isNull(objectValues.four) );
dump( StructKeyExists(objectValues,'four') );
// Unknown existing attribute
dump('five');
dump( isDefined('objectValues.five') );
dump( isNull(objectValues.five) );
dump( StructKeyExists(objectValues,'five') );
</cfscript>
What's your ColdFusion Version?
Actually a true JSON null value should be translated to 'null' by Coldfusion which is equally annoying but manageable
If you store just an empty string in the struct like this:
<cfset s = StructNew()>
<cfset s.test = "" />
<cfset json = SerializeJSON(s)>
<cfdump var="#json#">
<cfset d = DeserializeJSON(json)>
<cfdump var="#d#">
You get the following json: {"TEST":""}
and d.test is ""
But if you explicitly store a java null in the struct like this:
<cfset s = StructNew()>
<cfset s.test = javaCast( "null", "" ) />
<cfset json = serializeJSON(s)>
<cfdump var="#json#">
<cfset d = DeserializeJSON(json)>
<cfdump var="#d#">
you get the following json value: {"TEST":null}
and d.test is 'null'
This is probably more preferable as you could do this:
<cfif StructKeyExists(d,"TEST") AND Compare(d.test,"null") IS 0>

Formatting JSON Data with ColdFusion for HighCharts

So I'm working on a pie chart using HighSlide and ColdFusion.
To make this simple, for the data it expects a string like this:
data: [{name: 'Jane',y: 13}, {name: 'John',y: 23}, {name: 'Joe',y: 19}]
What i've done to accomplish this is to loop through my query results and create a string like this:
<cfloop query="getAreaCounts">
<cfset areaList = listAppend(areaList, "{name: '#name#',y: #y#}")>
</cfloop>
I know there has to be an easier/smarter way right? Since this is JSON data, i figured I could just do this:
<cfoutput>#SerializeJSON(getAreaCounts)#</cfoutput>
But that returns a JSON string like this which highcharts won't process:
{"COLUMNS":["Y","NAME"],"DATA":[[8,"Area1"],[7,"Area2"],[1,"Area3"],[1,"Area4"]]}
Any help pointing me in the right direction would be great. Do I need to dig deeper into a JSON - howto?
You're going to want to convert the query to an array of structs and then run serializeJSON() on that array.
Below is a method that I use pretty frequently when working with a lot of queries and JSON. I think that I got it from Ben Nadel's site and then converted it to cfscript.. I'll try to track down the blog post after I post this answer.
public array function queryToArray( required query qry ) {
var columns = arguments.qry.getColumnNames();
var ofTheJedi = [];
for( var i = 1; i LTE qry.recordCount; i++ ) {
var obj = {};
for( var k = 1; k LTE arrayLen( columns ); k++ ) {
structInsert( obj, columns[ k ], arguments.qry[ columns[ k ] ][ i ] );
}
arrayAppend( ofTheJedi, obj );
}
return ofTheJedi;
}
So, in your case you would do something like this:
<cfset myJSON = queryToArray( getAreaCounts ) />
<cfoutput>#serializeJSON( myJSON )#</cfoutput>
EDIT: Here's Ben's blog post that inspired the method above: http://www.bennadel.com/blog/124-Ask-Ben-Converting-a-Query-to-an-Array.htm
You need something like this (untested but you get the idea)
<cfscript>
data = [{name='Jane',y=13],{name='John',y=23},{name='Joe',y=19}];
</cfscript>
<cfoutput>#serializeJson(data)#</cfoutput>
You need to create a Coldfusion array of strucutres (each with "name" and "y" as members).. then serialize it. What you are serializeing above is the query object. By your code it looks like you meant to serialze the "arealist" variable - but that var would probably not come out correct either because it is not an array of structures - it is a list of strings.