ColdFusion : CoinBase API : CB-ACCESS-SIGN ERROR - coldfusion

I am almost there for getting the CoinBase API Sorted - but missing something or a bad conversion. I have tried a few things. This is best I can get too for building everything. I even setup a brand new key, in case old key.
I get a response from CoinBase : {"message":"invalid signature"}
If I purposely error out Key, Passphrase, Timestampe : it identifies those as incorrect. So all is right except this signature bit. I've included the Coinbase API Signature Details as well.
<cfset base_api = "https://api.exchange.coinbase.com/orders">
<cfset req_path = "/orders">
<cfset cb_key = "9999ac99999e6e99ee10dfd8ea5f9999">
<cfset cb_s = "999a7S/JZVX09EAX/LvWz9999/ALnVQptso999999dxrVfXfd999993OXAlfdPGwGZUKPBa99999pg1ubhVlsw==">
<cfset cb_pass = "999957agr99">
<cfset pair = "SHIB-USDT">
<cfset side = "sell">
<cfset type = "market">
<cfset size = "79940">
<cfset startDate = createdatetime( '1970','01','01','00','00','00' )>
<cfset datetimeNow = dateConvert( "local2Utc", now() )>
<cfset gmtnow = #DateAdd("h", 7, datetimeNow)#>
<cfset UnixStamp = datediff( 's', startdate, gmtnow )>
<cfscript>
cbs = #cb_s#;
body = SerializeJSON({
size: '#size#',
type: '#type#',
side: '#side#',
product_id: '#pair#'});
method = 'POST';
// create the prehash string by concatenating required parts
message = #UnixStamp# & method & #req_path# & body;
// decode the base64 secret
key = toBinary( #cb_s# );
// create sha256 hmac with the key
theHmac = hmac("#message#","#Key#", "HMACSHA256");
// encode the result
cas = toBase64(theHmac);
</cfscript>
<cfoutput>
<br>#cbs#<br>
<b>#cas#</b><br><br>
<br>#message#
</cfoutput>
<cfhttp url="#base_api#" method="post" result="result" charset="utf-8">
<cfhttpparam type="formfield" name="product_id" value="#pair#">
<cfhttpparam type="formfield" name="side" value="#side#">
<cfhttpparam type="formfield" name="type" value="#type#">
<cfhttpparam type="formfield" name="size" value="#size#">
<cfhttpparam type="header" name="CB-ACCESS-KEY" value="#cb_key#">
<cfhttpparam type="header" name="CB-ACCESS-PASSPHRASE" value="#cb_pass#">
<cfhttpparam type="header" name="CB-ACCESS-SIGN" value="#cas#">
<cfhttpparam type="header" name="CB-ACCESS-TIMESTAMP" value="#unixstamp#">
</cfhttp>
This is from COINBASE API Documentation
Signing a Message
The CB-ACCESS-SIGN header is generated by creating a sha256 HMAC using the base64-decoded secret key on the prehash string timestamp + method + requestPath + body (where + represents string concatenation) and base64-encode the output. The timestamp value is the same as the CB-ACCESS-TIMESTAMP header.
The body is the request body string or omitted if there is no request body (typically for GET requests).
The method should be UPPER CASE.
Remember to first base64-decode the alphanumeric secret string (resulting in 64 bytes) before using it as the key for HMAC. Also, base64-encode the digest output before sending in the header.
var crypto = require('crypto');
var cb_access_timestamp = Date.now() / 1000; // in ms
var cb_access_passphrase = '...';
var secret = 'PYPd1Hv4J6/7x...';
var requestPath = '/orders';
var body = JSON.stringify({
price: '1.0',
size: '1.0',
side: 'buy',
product_id: 'BTC-USD'
});
var method = 'POST';
// create the prehash string by concatenating required parts
var message = cb_access_timestamp + method + requestPath + body;
// decode the base64 secret
var key = Buffer(secret, 'base64');
// create a sha256 hmac with the secret
var hmac = crypto.createHmac('sha256', key);
// sign the require message with the hmac
// and finally base64 encode the result
var cb_access_sign = hmac.update(message).digest('base64');

I haven't used that API and unfortunately their documentation is sorely lacking any concrete examples of a generated signature. However, a good starting point would be to run the NodeJS example and output the results. Then attempt to match them with CF.
If you're not running NodeJS locally, use an online tool like JDoodle. For testing purposes, use a static timestamp and a valid base54 string for the secret. Output the generated values at the end of the script.
var crypto = require('crypto');
var cb_access_timestamp = 1645506818.784;
var secret = 'c2VjcmV0IHZhbHVl'; //"secret value" in plain text
// ... rest of code ...
console.log("cb_access_timestamp=", cb_access_timestamp);
console.log("secret=", secret);
console.log("requestPath=", requestPath);
console.log("body=", body);
console.log("method=", method);
console.log("body=", body);
console.log("cb_access_sign="+ cb_access_sign);
Next try and replicate those results in CF. Starting with the same static values
cb_access_timestamp = 1645506818.784;
secret = "c2VjcmV0IHZhbHVl"; // "secret value" in plain text
requestPath = '/orders';
method = 'POST';
Take special care when constructing json strings. Always assume API's are case sensitive, unlike CF. Your current code produces a json string with all upper case key names, (unlike the NodeJS example). To preserve the correct case, enclose the key names in quotes.
// use "[" and "]" to create ordered structure
bodyStruct = [
"price" : "1.0",
"size" : "1.0",
"side" : "buy",
"product_id" : "BTC-USD"
];
Also note that CF will be overly helpful and try and guess the data types of the structure values when serializing. You may need to use setMetaData() to force keys like price and size to be treated as strings, instead of numbers:
bodyStruct.setMetaData({
"price": {type : "string"}
, "size" : {type : "string"}
});
body = serializeJSON( bodyStruct );
Finally, don't get tripped up by encodings when converting the hmac value into base64. The current code passes the HMAC() result into ToBase64(). That produces the wrong result because ToBase64() can only convert plain strings (ascii, utf8, etc..) and HMAC() returns a hexadecimal encoded string. Instead use BinaryDecode() and BinaryEncode() to convert the hmac from hex to base64.
message = cb_access_timestamp & method & requestPath & body;
key = secret.binaryDecode("base64");
hmacHex = hmac( message, key, "HMACSHA256");
cb_access_sign = binaryEncode(hmacHex.binaryDecode("hex"), "base64");
If you output the CF results, they should match those from the NodeJS example.
// Results
writeDump({
"cb_access_timestamp": cb_access_timestamp
, "secret" : secret
, "requestPath" : requestPath
, "body" : body
, "method" : method
, "body" : body
, "cb_access_sign" : cb_access_sign
});
NodeJS Results:
cb_access_timestamp = 1645506818.784
secret = c2VjcmV0IHZhbHVl
requestPath = /orders
body = {"price":"1.0","size":"1.0","side":"buy","product_id":"BTC-USD"}
method = POST
body = {"price":"1.0","size":"1.0","side":"buy","product_id":"BTC-USD"}
cb_access_sign = neoPZ9ZKzQKoPGCmOuUVRY9v1NOpADQlOF+TZi2W8Qc=
CF Results:
Variable
Value
body
{"price":"1.0","size":"1.0","side":"buy","product_id":"BTC-USD"}
cb_access_sign
neoPZ9ZKzQKoPGCmOuUVRY9v1NOpADQlOF+TZi2W8Qc=
cb_access_timestamp
1645506818.784
method
POST
requestPath
/orders
secret
c2VjcmV0IHZhbHVl
If the results match, update the timestamp and secret and try passing the request parameters (size, type, product_id, side) in the request body, not separately as type="formfield"
cfhttp(url="#base_api#", method="post", result="result", charset="utf-8") {
cfhttpparam( type="header", name="CB-ACCESS-KEY", value="#cb_access_key#");
cfhttpparam( type="header", name="CB-ACCESS-PASSPHRASE", value="#cb_access_passphrase#");
cfhttpparam( type="header", name="CB-ACCESS-SIGN", value="#cb_access_sign#");
cfhttpparam( type="header", name="CB-ACCESS-TIMESTAMP", value="#cb_access_timestamp#");
cfhttpparam( type="header", name="Content-Type", value="application/json");
cfhttpparam( type="body", value="#body#");
}

Related

Power BI Iterative API Loop

I am attempting (and can successfully do so) to connect to an API and loop through several iterations of the API call in order to grab the next_page value, put it in a list and then call the list.
Unfortunately, when this is published to the PBI service I am unable to refresh there and indeed 'Data Source Settings' tells me I have a 'hand-authored query'.
I have attempted to follow Chris Webbs' blog post around the usage of query parameters and relative path, but if I use this I just get a constant loop of the first page that's hit.
The Start Epoch Time is a helper to ensure I only grab data less than 3 months old.
let
iterations = 10000, // Number of MAXIMUM iterations
url = "https://www.zopim.com/api/v2/" & "incremental/" & "chats?fields=chats(*)" & "&start_time=" & Number.ToText( StartEpochTime ),
FnGetOnePage =
(url) as record =>
let
Source1 = Json.Document(Web.Contents(url, [Headers=[Authorization="Bearer MY AUTHORIZATION KEY"]])),
data = try Source1[chats] otherwise null, //get the data of the first page
next = try Source1[next_page] otherwise null, // the script ask if there is another page*//*
res = [Data=data, Next=next]
in
res,
GeneratedList =
List.Generate(
()=>[i=0, res = FnGetOnePage(url)],
each [i]<iterations and [res][Data]<>null,
each [i=[i]+1, res = FnGetOnePage([res][Next])],
each [res][Data])
Lookups
If Source1 exists, but [chats] may not, you can simplify
= try Source1[chats] otherwise null
to
= Source1[chats]?
Plus it you don't lose non-lookup errors.
m-spec-operators
Chris Web Method
should be something closer to this.
let
Headers = [
Accept="application/json"
],
BaseUrl = "https://www.zopim.com", // very important
Options = [
RelativePath = "api/v2/incremental/chats",
Headers = [
Accept="application/json"
],
Query = [
fields = "chats(*)",
start_time = Number.ToText( StartEpocTime )
],
Response = Web.Contents(BaseUrl, Options),
Result = Json.Document(Response) // skip if it's not JSON
in
Result
Here's an example of a reusable Web.Contents function
helper function
let
/*
from: <https://github.com/ninmonkey/Ninmonkey.PowerQueryLib/blob/master/source/WebRequest_Simple.pq>
Wrapper for Web.Contents returns response metadata
for options, see: <https://learn.microsoft.com/en-us/powerquery-m/web-contents#__toc360793395>
Details on preventing "Refresh Errors", using 'Query' and 'RelativePath':
- Not using Query and Relative path cause refresh errors:
<https://blog.crossjoin.co.uk/2016/08/23/web-contents-m-functions-and-dataset-refresh-errors-in-power-bi/>
- You can opt-in to Skip-Test:
<https://blog.crossjoin.co.uk/2019/04/25/skip-test-connection-power-bi-refresh-failures/>
- Debugging and tracing the HTTP requests
<https://blog.crossjoin.co.uk/2019/11/17/troubleshooting-web-service-refresh-problems-in-power-bi-with-the-power-query-diagnostics-feature/>
update:
- MaybeErrResponse: Quick example of parsing an error result.
- Raw text is returned, this is useful when there's an error
- now response[json] does not throw, when the data isn't json to begin with (false errors)
*/
WebRequest_Simple
= (
base_url as text,
optional relative_path as nullable text,
optional options as nullable record
)
as record =>
let
headers = options[Headers]?, //or: ?? [ Accept = "application/json" ],
merged_options = [
Query = options[Query]?,
RelativePath = relative_path,
ManualStatusHandling = options[ManualStatusHandling]? ?? { 400, 404, 406 },
Headers = headers
],
bytes = Web.Contents(base_url, merged_options),
response = Binary.Buffer(bytes),
response_metadata = Value.Metadata( bytes ),
status_code = response_metadata[Response.Status]?,
response_text = Text.Combine( Lines.FromBinary(response,null,null, TextEncoding.Utf8), "" ),
json = Json.Document(response),
IsJsonX = not (try json)[HasError],
Final = [
request_url = metadata[Content.Uri](),
response_text = response_text,
status_code = status_code,
metadata = response_metadata,
IsJson = IsJsonX,
response = response,
json = if IsJsonX then json else null
]
in
Final,
tests = {
WebRequest_Simple("https://httpbin.org", "json"), // expect: json
WebRequest_Simple("https://www.google.com"), // expect: html
WebRequest_Simple("https://httpbin.org", "/headers"),
WebRequest_Simple("https://httpbin.org", "/status/codes/406"), // exect 404
WebRequest_Simple("https://httpbin.org", "/status/406"), // exect 406
WebRequest_Simple("https://httpbin.org", "/get", [ Text = "Hello World"])
},
FinalResults = Table.FromRecords(tests,
type table[
status_code = Int64.Type, request_url = text,
metadata = record,
response_text = text,
IsJson = logical, json = any,
response = binary
],
MissingField.Error
)
in
FinalResults

trying to get the data like this from the cfc function

I have a cfc where i need t return the headers for the jqgrid for binding dynamically
i am trying to query the data like this:
colNames: ['ID', 'Institution Name', 'Display Name', 'Short Name', 'Board of Education', 'Scheme Name','Subscription Date'],
colModel: [
{ name: 'institutionid', sortable: true, },
{ name: 'institutionname', sortable: true },
{ name: 'displayname', sortable: true },
{ name: 'shortname' ,sortable: true},
{ name: 'supportedfield', sortable: true },
{ name: 'schemename', sortable: true },
{ name: 'subscriptionto', sortable: true}
]
i can easily get the colNames, but for colmodal how can i bring the elements of sort: true for all by default and format should be like arrayofstructs
Thanks
Query try
<cffunction name="headers" localmode="modern" access="remote" returnformat="json" hint="Handles the Functionality of returning the Table Headers">
<cfset columnsInfos = {}>
<cfset returnArray = []>
<cfset cList = QueryExecute("select top 1 * from mytable").columnList>
<cfset cListQueryObj = QueryNew(cList)>
<cfdump var="#cListQueryObj#" abort>
<cfset colNames = ListtoArray(cList)>
<cfloop query="#cListQueryObj#">
<cfset rowStruct = {}>
<cfloop list="#cList#" index="colname">
<cfset "rowStruct['#colname#']" = cListQueryObj[colname]>
</cfloop>
<cfset arrayAppend(returnArray,rowStruct)>
<cfdump var="#rowStruct#">
</cfloop>
<cfset columnsInfos["colModel"] = returnArray>
<cfset columnsInfos["colNames"] = colNames>
<cfreturn columnsInfos>
</cffunction>
Serializing a query object won't return the expected result. Since you are using the built-in returnFormat="json", the easiest (and probably only) approach is working with an array of structs, just like your JS example shows:
<cffunction name="headers" localmode="modern" access="public" returnformat="json" hint="Handles the Functionality of returning the Table Headers">
<!--- best practise: declare the returned scheme --->
<cfset result = {
"colNames": [],
"colModel": []
}>
<!--- get your table's columns (mockup) --->
<cfset columnList = "institutionid,institutionname,displayname,shortname,supportedfield,schemename,subscriptionto">
<!--- use the column names from your query? --->
<cfset result["colNames"] = listToArray(columnList)>
<!--- add an entry with "name" and "sortable" for every column --->
<cfloop list="#columnList#" index="columnName">
<cfset result["colModel"].add({
"name": columnName,
"sortable": true
})>
</cfloop>
<cfreturn result>
</cffunction>
Note that we are not calling serializeJSON on the result, because returnFormat="json" will already do that for us. Regarding your colNames: Your JS example uses mapped column names, not dynamic ones as shown in your code and the code above. You might want to make them either static or map them yourself.
I like Alex's approach, but I like cfscript better. I also like localized variables.
<cfscript>
/**
* #hint Handles the Functionality of returning the Table Headers
* #output false
* #returnFormat JSON
*/
public any function headers() {
// best practise: declare the returned scheme
var result = {
"colNames": [],
"colModel": []
};
// get your table's columns (mockup)
var columnList = "institutionid,institutionname,displayname,shortname,supportedfield,schemename,subscriptionto";
// use the column names from your query?
result.colNames = listToArray(columnList);
// add an entry with "name" and "sortable" for every column
for (var columnName in columnList) {
result.colModel.add({
"name": columnName,
"sortable": true
});
}
return result;
}
writedump(headers);
writedump(headers());
</cfscript>
Function signature
Function results
Also see
JSON response using cfscript function

Creating JWT in Coldfusion for google Service account

I'm confused on the signature aspect for a JWT. I believe I have the header and claim set correct as I got past any errors was seeing when originally writing this. My question is mostly around the signature. IS HMac with HMACSHA256 correct? I think I may be confused on where to get the private key for encryption. If anyone has some guidance that would be great.
<cfset JWT_header = structNew()>
<cfset JWT_header['alg'] = 'RS256'>
<cfset JWT_header['typ'] = 'JWT'>
<cfset JWT_header = serializeJSON(JWT_header)>
<cfset JWT_claim_set = structNew()>
<cfset JWT_claim_set['iss'] = 'secret_iss'>
<cfset JWT_claim_set['scope'] = 'my_scope'>
<cfset JWT_claim_set['aud'] = 'https://www.googleapis.com/oauth2/v4/token'>
<cfset JWT_claim_set['exp'] = 'Time_Stamp')>
<cfset JWT_claim_set['iat'] = 'Time_Stamp')>
<cfset JWT_claim_set = serializeJSON(JWT_claim_set)>
<cfset data = ToBase64(JWT_header) & '.' & ToBase64(JWT_claim_set)>
<cfset hashedData = HMac(data, 'my_secret_private_key','HMACSHA256')>
<cfset signature = toBase64(hashedData)>
<cfset JWT = data & '.' & signature>
<cfhttp url="https://www.googleapis.com/oauth2/v4/token" method="post" result="result">
<cfhttpparam name="grant_type" type="formField" value="urn:ietf:params:oauth:grant-type:jwt-bearer" />
<cfhttpparam name="assertion" type="formField" value="#JWT#" />
</cfhttp>
<cfoutput>#result.filecontent#</cfoutput>
This returns:
'{ "error": "invalid_grant", "error_description": "Invalid JWT Signature." }'
For anyone that needs some code in the future to get them on the right path. This code is used for the firebase messaging api for push notification but can be adapted for other google services.
<cfscript>
variables.service_json = deserializeJSON(fileRead(expandPath('./serviceaccountprivatekey.json')));
variables.timestamp = dateDiff("s", CreateDate(1970,1,1), now());
variables.timestampUTC = timestamp + 21600; //add 6 hour to convert to utc
//generate jwt
variables.jwt_header = {
'alg': 'RS256',
'typ': 'JWT'
};
variables.jwt_header = serializeJSON(variables.jwt_header);
variables.jwt_header = toBase64(variables.jwt_header);
variables.jwt_claim = {
'iss': service_json.client_email,
'scope': 'https://www.googleapis.com/auth/firebase.messaging',
'aud': 'https://www.googleapis.com/oauth2/v4/token',
'iat': timestampUTC,
'exp': (timestampUTC + 3600)
};
variables.jwt_claim = serializeJSON(variables.jwt_claim);
variables.jwt_claim = toBase64(variables.jwt_claim);
variables.jwt = variables.jwt_header & '.' & variables.jwt_claim;
//sign jwt
variables.keyText = reReplace( service_json.private_key, "-----(BEGIN|END)[^\r\n]+", "", "all" );
variables.keyText = trim( keyText );
variables.privateKeySpec = createObject( "java", "java.security.spec.PKCS8EncodedKeySpec" )
.init(binaryDecode( variables.keyText, "base64" ));
variables.privateKey = createObject( "java", "java.security.KeyFactory" )
.getInstance( javaCast( "string", "RSA" ) )
.generatePrivate( privateKeySpec );
variables.signer = createObject( "java", "java.security.Signature" )
.getInstance( javaCast( "string", 'SHA256withRSA' ));
variables.signer.initSign( variables.privateKey );
variables.signer.update( charsetDecode( variables.jwt, "utf-8" ) );
variables.signedBytes = signer.sign();
variables.signedBase64 = toBase64(signedBytes);
variables.jwt_signed = variables.jwt & '.' & variables.signedBase64;
</cfscript>
<cfhttp
url="https://www.googleapis.com/oauth2/v4/token"
method="POST"
result="res"
>
<cfhttpparam name="grant_type" type="formField" value="urn:ietf:params:oauth:grant-type:jwt-bearer" />
<cfhttpparam name="assertion" type="formField" value="#variables.jwt_signed#" />
</cfhttp>
<cfset variables.res = deserializeJSON(res.filecontent) />
<cfscript>
variables.body = {
"message": {
"notification": {
"title": "test",
"body": "test test test"
},
"token": "e7blahblahSQ:thisisanexamplefirebasemessengingtokenpleaseputyourownonehere"
}
};
</cfscript>
<cfhttp url="https://fcm.googleapis.com/v1/projects/{project_id}/messages:send" method="post" result="res">
<cfhttpparam type="header" name="Content-type" value="application/json" />
<cfhttpparam type="header" name="Authorization" value="Bearer #variables.res.access_token#" />
<cfhttpparam type="body" value="#serializeJSON(body)#" />
</cfhttp>
<cfdump var="#res.fileContent#">
I got this to work with Ben Nadel's code(https://www.bennadel.com/blog/2941-experimenting-with-rsa-encrypted-signature-generation-and-verification-in-coldfusion.htm), but I had to modify it to work. I commented out anything to do with a public key as I was not using one to interface with google. If I was to enhance it I could create logic to look for the use of public or private key. Next I skipped anything with Pem file formatting since google isn't using that. Now it works.

Custom function works in ColdFusion 10/11 but throws error in Lucee

I have the following function, inherited from some CF code. It works, but only in ColdFusion version 10 and 11. I am not concerned with 2016 as no plans for upgrade. I'm trying to run it under Lucee, but it is not working.
<cffunction name="QueryToArray" access="public" returntype="array" output="false" hint="This turns a query into an array of structures.">
<!--- Define arguments. --->
<cfargument name="Data" type="query" required="yes" />
<cfscript>
var LOCAL = StructNew(); // Define the local scope.
LOCAL.Columns = data.getMetaData().getColumnLabels(); // Get the column names as an array.
LOCAL.QueryArray = ArrayNew(1); // Create an array that will hold the query equivalent.
for (LOCAL.RowIndex = 1 ; LOCAL.RowIndex LTE ARGUMENTS.Data.RecordCount;
LOCAL.RowIndex = (LOCAL.RowIndex + 1)){
LOCAL.Row = StructNew();
for (LOCAL.ColumnIndex = 1 ; LOCAL.ColumnIndex LTE ArrayLen(LOCAL.Columns);
LOCAL.ColumnIndex = (LOCAL.ColumnIndex + 1)){
LOCAL.ColumnName = LOCAL.Columns[LOCAL.ColumnIndex];
LOCAL.Row[LOCAL.ColumnName] = ARGUMENTS.Data[LOCAL.ColumnName][LOCAL.RowIndex];
}
ArrayAppend(LOCAL.QueryArray, LOCAL.Row);
}
return(LOCAL.QueryArray);
</cfscript>
</cffunction>
In Lucee, I tried making these changes:
<cffunction name="QueryToArray" access="public" returntype="array" output="false" hint="This turns a query into an array of structures.">
<!--- Define arguments. --->
<cfargument name="Data" type="query" required="yes" />
<cfscript>
var LOCAL = StructNew(); // Define the local scope.
LOCAL.Columns = data.getColumnlist(false); // Get the column names as an array.
LOCAL.QueryArray = ArrayNew(1); // Create an array that will hold the query equivalent.
for (LOCAL.RowIndex = 1 ; LOCAL.RowIndex LTE ARGUMENTS.Data.RecordCount;
LOCAL.RowIndex = (LOCAL.RowIndex + 1)){
LOCAL.Row = StructNew();
for (LOCAL.ColumnIndex = 1 ; LOCAL.ColumnIndex LTE ArrayLen(LOCAL.Columns);
LOCAL.ColumnIndex = (LOCAL.ColumnIndex + 1)){
LOCAL.ColumnName = LOCAL.Columns[LOCAL.ColumnIndex];
LOCAL.Row[LOCAL.ColumnName] = ARGUMENTS.Data[LOCAL.ColumnName][LOCAL.RowIndex];
}
ArrayAppend(LOCAL.QueryArray, LOCAL.Row);
}
return(LOCAL.QueryArray);
</cfscript>
</cffunction>
but I'm getting this error:
Message: Can't cast String [name,value] to a value of type [Array]
Detail: Java type of the object is java.lang.String
Stacktrace:
The Error Occurred in
C:\home\website\wwwroot\controller\production\cfc\kernel.cfc: line 12
10: LOCAL.RowIndex = (LOCAL.RowIndex + 1)){
11: LOCAL.Row = StructNew();
12: for (LOCAL.ColumnIndex = 1 ; LOCAL.ColumnIndex LTE ArrayLen(LOCAL.Columns);
13: LOCAL.ColumnIndex = (LOCAL.ColumnIndex + 1)){
14: LOCAL.ColumnName = LOCAL.Columns[LOCAL.ColumnIndex];
To get your code to work, all you need to do is change line 6 from
LOCAL.Columns = data.getColumnlist(false); // Get the column names as an array.
to this
LOCAL.Columns = data.getColumnNames(); // Get the column names as an array.
However I found a gist here that works in both ColdFusion and Lucee. I decided to create some sample code and test it at https://trycf.com/. I verified that it works in Lucee 4.5 and 5. It also works in ColdFusion 10, 11, 2016 as well. Hope this helps!
<!--- Create a new three-column query, specifying the column data types --->
<cfset myQuery = QueryNew("Name, Time, Advanced", "VarChar, Time, Bit")>
<!--- Make two rows in the query --->
<cfset QueryAddRow(MyQuery, 2)>
<!--- Set the values of the cells in the query --->
<cfset QuerySetCell(myQuery, "Name", "The Wonderful World of CMFL", 1)>
<cfset QuerySetCell(myQuery, "Time", "9:15 AM", 1)>
<cfset QuerySetCell(myQuery, "Advanced", False, 1)>
<cfset QuerySetCell(myQuery, "Name", "CFCs for Enterprise
Applications", 2)>
<cfset QuerySetCell(myQuery, "Time", "12:15 PM", 2)>
<cfset QuerySetCell(myQuery, "Advanced", True, 2)>
<h4>The query</h4>
<cfdump var="#myQuery#">
<h4>The array of objects</h4>
<cfset myArray = QueryToArray(myQuery)>
<cfdump var="#myArray#">
<cfscript>
/**
* #hint Returns reasonable array of objects from a cfquery
*/
public function queryToArray(
required query query,
string excludeColumns = ""
){
if (server.coldfusion.productName == "ColdFusion Server") {
local.columns = arguments.query.getMetaData().getColumnLabels();
} else if (server.coldfusion.productName == "Lucee") {
local.columns = arguments.query.getColumnNames();
} else {
local.columns = [];
}
local.response = arrayNew(1);
for (local.rowIndex=1; local.rowIndex<=arguments.query.recordCount; local.rowIndex++) {
local.response[local.rowIndex] = structNew();
for (local.columnIndex=1; local.columnIndex<=arrayLen(local.columns); local.columnIndex++) {
local.columnName = local.columns[local.columnIndex];
if(local.columnName != "" && (arguments.excludeColumns == "" || !listFindNoCase(arguments.excludeColumns, local.columnName))) {
local.response[local.rowIndex][local.columnName] = arguments.query[local.columnName][local.rowIndex];
}
}
}
return local.response;
}
</cfscript>

JSON contains Special Characters

My JSON contains special characters like: new line, double quotes etc.
I am creating the JSON using Coldfusion server side script. But in case of special chars I get error due to wrongly formatted JSON. What should I do in such a case?
<cfoutput>
[
<cfset loopIndex=0>
<cfloop query="qEvents">
<cfif loopIndex NEQ 0>,</cfif>
<cfif is_allday EQ 1>
<cfset isallDayEvent = "true">
<cfelse>
<cfset isallDayEvent = "false">
</cfif>
{
"title": "#title#",
"start": "#DateFormat(start_date_time,'mm/dd/yyyy')# #TimeFormat(start_date_time,'hh:mm tt')#",
"end": "#DateFormat(end_date_time,'mm/dd/yyyy')# #TimeFormat(end_date_time,'hh:mm tt')#",
"allDay": #isallDayEvent#,
"eventID": "#event_id#",
"duration": "#duration#",
"note": "#note#",
"location": "#location#"
}
<cfset loopIndex=loopIndex+1>
</cfloop>
]
</cfoutput>
Rather than writing the JSON by hand, you should generate an array of structs and then use serializeJSON() to convert it to a valid JSON string:
<cfset thisArrayBecomesJSON = [] />
<cfloop query="qEvents">
<cfif is_allday EQ 1>
<cfset isAllDayEvent = "true" />
<cfelse>
<cfset isAllDayEvent = "false" />
</cfif>
<cfset thisEvent = {
'title' = title,
'start' = dateFormat( start_date_time, 'mm/dd/yyyy' ) & timeFormat( start_date_time, 'hh:mm tt' ),
'end' = dateFormat( end_date_time, 'mm/dd/yyyy' ) & timeFormat( end_date_time, 'hh:mm tt' ),
'allDay' = isAllDayEvent,
'eventID' = event_id,
'duration' = duration,
'note' = note,
'location' = location
} />
<cfset arrayAppend( thisArrayBecomesJSON, thisEvent ) />
</cfloop>
<cfset myJSON = serializeJSON( thisArrayBecomesJSON ) />
<cfoutput>#myJSON#</cfoutput>
This is untested, but I think it should work ok - there may be some syntax errors.