I have a coldfusion Struct containing mix keys numeric and alpha, alphanumerics
I need to access only the numeric keys.
My code looks like
<cfset ids = structkeyList(st ) />
<cfset numericIDs = "" />
<cfloop list="#ids#" index="i">
<cfif IsNumeric(i)>
<cfset numericIDs = ListAppend( numericIDs , i ) />
</cfif>
</cfloop>
Is there a better method to solve such problems?
Is there a better method to solve such problems?
I would use something like this:
<cfset numericIDs = arrayToList(reMatch('\b\d+(?=,|$)\b', structKeyList(st)))>
Is there a better method to solve such problems?
I'd generally recommend working with arrays instead of lists.
In CF9 a loop similar to yours is as good as it gets. You can make a utility function out of it if you need it more than once. This one avoids StructKeyList() to be able to deal with all kinds of keys, independent of a separator character:
<cfscript>
function GetNumericKeys(struct) {
var keys = struct.keys();
var result = ArrayNew(1);
var key = "";
while (keys.hasNext()) {
key = keys.next();
if (IsNumeric(key)) ArrayAppend(result, key);
}
return result;
}
</cfscript>
and
<cfset nkeys = GetNumericKeys(st)>
In CF11 you can get a little more sophisticated (tested on CF11, can't say how CF10 handles this code).
<cfscript>
numericIDs = arrayFilter(structKeyArray(st), function (key) {
return IsNumeric(key);
});
</cfscript>
To ensure integer keys, use:
<cfscript>
numericIDs = arrayFilter(structKeyArray(st), function (key) {
return Int(key) eq key;
});
</cfscript>
I really don't see what's wrong with this. It should work quite well already, and it is very readable.
Sometimes working with a List is faster than an Array.
I had this:
<cfscript> function ListNumeric(principal) {
a=principal;
cleanlist = ''; for (i=1; i <= ListLen(a);i=i+1) { if(IsNumeric(ListGetAt(a,i))){ cleanlist = ListAppend(cleanlist,ListGetAt(a,i)); } } Return cleanlist; } </cfscript>
Also possible to work with regular expression:
inList2 = REReplace(inList,"[^0-9.]", "","ALL");
Related
I would like to loop over query and compare column values. Here is example of CFML code:
<cfquery name="qryUserPerm" datasource="#Application.dsn#">
SELECT AccessType, AccessLevel, State, City, Building
FROM Permissions
WHERE AccountID = <cfqueryparam cfsqltype="cf_sql_integer" value="#trim(session.AccountID)#">
</cfquery>
<cfset local.permissionType = "">
<cfset local.permissionLevel = "">
<cfset local.permissionList = "">
<cfif qryUserPerm.AccessLevel EQ "S">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = qryUserPerm.State>
<cfelseif qryUserPerm.AccessLevel EQ "C">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = ListRemoveDuplicates(ValueList(permissionList,qryUserPerm.City))>
<cfelseif qryUserPerm.AccessLevel EQ "B">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = ListRemoveDuplicates(ValueList(permissionList,qryUserPerm.Building))>
</cfif>
Code above should be translated to cfscript, I got this far but can't figure it out how to access column values.
<cfscript>
public string function permissionList(required string AccountID) {
local.fnResults = "";
local.permissionList = "";
try{
local.qryPermissions = new Query();
local.qryPermissions.setDatasource("#Application.dsn#");
local.qryPermissions.setSQL("SELECT AccessType, AccessLevel, State, City, Building FROM Permissions WHERE AccountID = :AccountID");
local.qryPermissions.addParam(name="AccountID",value="#trim(arguments.AccountID)#",cfsqltype="cf_sql_idstamp");
local.qryRes = qryPermissions.execute();
for ( i = 1 ; i <= qryRes.getResult().recordCount ; i++ ) {
if(qryRes["AccessLevel"][i] EQ "S"){
local.permissionList = "";
}else if(qryRes["AccessLevel"][i] EQ "S"){
local.permissionList = ListRemoveDuplicates(ValueList(qryRes.Agency,","));
}else if(qryRes["AccessLevel"][i] EQ "C"){
local.permissionList = ListRemoveDuplicates(ValueList(qryRes.District,","));
}else if(qryRes["AccessLevel"][i] EQ "B"){
local.permissionList = ListRemoveDuplicates(ValueList(qryRes.Building,","));
}
}
local.fnResults = permissionList;
}catch(any e){
local.fnResults = e.message;
//writeOutput(e.message);
}
return fnResults;
}
writeOutput(permissionList(AccountID));
</cfscript>
If anyone can help please let me know.
(From comments ...)
The issue is local.qryRes doesn't actually contain a query object. Confusingly, calling execute() doesn't return a query, but calling execute().getResult() does. Try changing the assignment from:
local.qryRes = qryPermissions.execute();
To:
local.qryRes = qryPermissions.execute().getResult();
A few other observations:
It is important to local scope ALL function variables, including your loop index i. Otherwise, you may get some bizarre and unpredictable results if the component is stored in a shared scope.
Although I don't think a loop is necessary, if you do loop, consider the simpler for..in syntax, instead of an indexed loop:
for (local.row in local.qryPermissions ) {
if (local.row.AccessType eq "S") {
//... code here
}
....
}
Since the access fields are so closely related, I'd probably have the function return a structure containing all three keys (AccessType, AccessLevel, PermissionList) rather than having three separate functions.
Rather than using a loop, consider going with one of the suggestions on your other thread,
Best way to store permissions for the user account?
You can also use :
local.qryPermissions = queryExecute(
"SELECT AccessType, AccessLevel, State, City, Building
FROM Permissions
WHERE AccountID = :AccountID" ,
{AccountID={value="#trim(arguments.AccountID)#", cfsqltype="cf_sql_idstamp"}} // Or "?" and "[value=xxx,cfsqltype=xxx]"
) ;
And then just build out your permissions pieces without the loop:
local.permissionType = qryPermissions.AccessType ;
local.permissionLevel = qryPermissions.AccessLevel ;
switch( qryPermissions.AccessLevel ) {
case "S" : local.permissionList = qryPermissions.State ;
break ;
case "C" : local.permissionList = ListRemoveDuplicates(ValueList(qryPermissions.City)) ;
break ;
case "B" : local.permissionList = ListRemoveDuplicates(ValueList(qryPermissions.Building)) ;
break ;
}
Also see my notes on the other question about potential for unintentional, semi-related data.
Is there a simple way to serialize a single-level structure as a string for use in a url?
for example:
?key1=val1&key2=val2
<cfscript>
// create simple struct
x = { a=1, b=2, c=3 };
WriteDump(x);
// serialize in JSON format and encode for URL transport
y = URLEncodedFormat( SerializeJSON(x));
WriteOutput( 'url: #SCRIPT_NAME#?#y#');
// now receive the URL variable and dump it
if ( StructKeyExists( url, 'z' )) {
writeOutput( '<h3>URL Data:</h3>' );
writeDump( DeserializeJSON( URLDecode( z)));
}
</cfscript>
How does this look?
<cfset tmpStruct = {"firstItem" = "one", "secondItem" = "two"} />
<cfset myUrl = "http://domain.com/file.cfm?" />
<cfloop list="#structKeyList(tmpStruct)#" index="i" >
<cfset myUrl = myUrl & i & "=" & tmpStruct[i] & "&" />
</cfloop>
<cfset myUrl = left(myUrl,len(myUrl)-1) />
<cfdump var="#myUrl#" />
I am facing a weird issue.
When I am consuming the below snippet of code as a webservice residing in a CF9 server I am getting the error "The value coldfusion.runtime.Struct cannot be converted to a number."
The call returns an array of structures. I would like to create a query from this array of structure. When I place this code as a standalone code in my local server(CF10) it works fine. But as soon i place it in the remote server to be invoked i get the error.
I almost pulled out my hair when I got the same error message even when I replaced the variable 'tempstruct' with a hard coded structure. As soon I remove the QueryAddRow I am able to return anything.
Any help is appreciated.
<cfset myquery=querynew("category,category_id,event_description","varchar,integer,varchar")>
<cfinvoke
webservice="http://199.99.99.999/vod_queries.cfc?wsdl"
method="getAllCategoryByResort"
returnvariable="arrAllSpaEvents"
refreshwsdl="true" >
<cfinvokeargument name="Resort" value="SRB" >
</cfinvoke>
<cfif arraylen(arrAllSpaEvents) GT 0>
<cfloop array="#arrAllSpaEvents#" index="cur_row">
<cfset tempstruct=StructNew()>
<cfset tempstruct.CATEGORY=cur_row.CATEGORY>
<cfset tempstruct.CATEGORY_ID=cur_row.CATEGORY_ID>
<cfset tempstruct.EVENT_DESCRIPTION=cur_row.EVENT_DESCRIPTION>
<cfset QueryAddRow(myquery,#tempstruct#)>
</cfloop>
</cfif>
<cfreturn myquery>
You almost got.
However, indeed you are using new CF10 overloading in CF9. What's more, if you were using CF10, it looks like you could stuff the whole top array in with looping like that.
But you can almost do the same thing. CF9 will take an array overload for the value.
Not quite as clean as CF10 but you do what you can.
Also, the extra # signs are superfluous.
Here is an example with something that your data might look like:
<cfscript> // I did this all in a cfscript block for simplicity
Your retrieved data might look something like this guessin from example
arrAllSpaEvents = [
{category='fun', category_id=1, event_description='massage'},
{category='work', category_id=2, event_description='spinning'},
{category='beauty', category_id=3, event_description='mani'},
{category='beauty', category_id=3, event_description='pedi'}
];
Create a more useful struct to build the query dynamically
s = {
category = {colType = 'varchar', colVals = []},
category_id = {colType = 'integer', colVals = []},
event_description = {colType = 'varchar', colVals = []}
};
This is looping the data to fill the colVals arrays
for(c = 1; c <= arrAllSpaEvents.size(); c++ ) {
for(k in arrAllSpaEvents[c]) {
s[k].colVals[c] = arrAllSpaEvents[c][k];
}
}
This is the short form of the same double loop above in a single line
for(c = 1; c <= arrAllSpaEvents.size(); c++ ) for(k in arrAllSpaEvents[c]) s[k].colVals[c] = arrAllSpaEvents[c][k];
Now build your query. Start with an empty query (pass in a blank);
q = queryNew('');
Then loop your struct and using the keys for the column names (for simplicity they are the same key)
for(k in s ) queryAddColumn(q,k,s[k].colType,s[k].colVals);
Verify your struct and query:
writedump(s);
writedump(q);
</cfscript>
I ran this in CF9 so should work fine for you.
This should get you going.
Here's a really bizarre bug I recently ran across in CF9. Anyone have a clue why it is occurring and if either I am doing something wrong, or there is a hotfix available. Look at the following code. We take a string, add an A, add a B, and then attempt to add a C... yet the result we get is "ababc". The expected result is "abc". The bug only occurs if you do a named argument AND an implicit struct in argument pass AND a &= operator in the function call. If any of those 3 cases is not there, the bug does not occur. Any ideas why?
<cffunction name="test">
<cfargument name="widget">
<cfset var locals = StructNew()>
<cfreturn arguments.widget.value>
</cffunction>
<cfset return = "">
<cfset return &= "a">
<cfset return &= "b">
<cfset return &= test(widget = { value = "c" })>
<cfoutput>#return#</cfoutput>
Well: you've kinda answered your own question here: it happens because it's a bug. Bugs happen. It's good you've taken the time to advise Adobe about it.
As for work arounds, these two variations work fine:
<cfset return = "">
<cfset return &= "a">
<cfset return &= "b">
<cfset st = { value = "c" }><!--- refactor where the struct is created --->
<cfset return &= test(widget = st)>
<cfoutput>#return#</cfoutput>
Or:
<cfset return = "">
<cfset return &= "a">
<cfset return &= "b">
<cfset temp = test(widget = { value = "c" })><!--- refactor where the function is called --->
<cfset return &= temp>
<cfoutput>#return#</cfoutput>
You're just gonna have to do something like that until Adobe gets around to fixing it :-(
In versions of ColdFusion prior to 8 the duplicate function throws an error if there are any components in the structure. In 8 and beyond it will work, but there are issues when copying components.
So, What I need is a way to create a deep copy of a structure that ignores components. For my purposes it's for debugging, I need a snapshot of the variables scope at a particular point in the code, so efficiency doesn't really matter as this will never make it out of the development environment. Currently using CF 7, I would take what 8 offers if only to solve this immediate issue, but I don't control upgrade :(
While you were off killing brain cells, I took a stab at a recursive function ;) It excludes components and java/com objects. Neither of which MX7 can duplicate. I threw the functions into a component to avoid tampering with the variables scope. Then stored the instance in the request scope.
It is not rigorously tested. So I am sure there is room for improvement.
Usage
<cfset request.util = createObject("component", "Util")>
<cfset request.copy = request.util.duplicateStructMinusObjects(variables)>
<cfdump var="#request.copy#">
Util.cfc
<cfcomponent>
<cfscript>
function duplicateArrayMinusObjects(input) {
var x = "";
var value = "";
var output = arrayNew(1);
for (x = 1; x lte arrayLen(arguments.input); x = x + 1) {
value = arguments.input[x];
// note components are considered structures
if (IsStruct(value) and not IsObject(value)) {
arrayAppend(output, duplicateStructMinusObjects(value));
}
else if (IsArray(value)) {
arrayAppend(output, duplicateArrayMinusObjects(value));
}
else if (not IsObject(value)){
arrayAppend(output, duplicate(value));
}
}
return output;
}
function duplicateStructMinusObjects(input) {
var key = "";
var value = "";
var output = structNew();
for (key in arguments.input) {
value = arguments.input[key];
// note components are considered structures
if (IsStruct(value) and not IsObject(value)) {
output[key] = duplicateStructMinusObjects(value);
}
else if (IsArray(value)) {
output[key] = duplicateArrayMinusObjects(value);
}
else if (not IsObject(value)){
output[key] = duplicate(value);
}
}
return output;
}
</cfscript>
</cfcomponent>
Doesn't matter how long you think/search, you always come up with the answer right after you ask the question.
I was able to solve this by deliberately mis-using try/catch, so I looped through the structure, did a try on creating an object out of a each item as if it were a component, and on error, copied it to my snapshot structure. I also had to store it in a different scope, in my case I used session, since if I let it go to the default variables, there would be a circular reference that cause a structure with an infinite number of children.
EDIT: THIS DOES NOT DO WHAT I THOUGHT IT DID, SEE BELOW
<cfset session.varSnapShot = StructNew()>
<cfset loopList = StructKeyList(variables)>
<cfloop from="1" to="#ListLen(loopList)#" index="i">
<cftry>
<cfobject name="x#i#" component="#variables[ListGetAt(loopList,i)]#">
<cfcatch>
<cfset session.varSnapShot[ListGetAt(loopList,i)]= variables[ListGetAt(loopList,i)]>
</cfcatch>
</cftry>
</cfloop>
EDIT: Since the above doesn't actually do a deep copy (thanks Leigh) I came up with this:
<cfloop from="1" to="#ListLen(loopList)#" index="i">
<cfset metaData = GetMetaData(variables[ListGetAt(loopList,i)])>
<cfif isStruct(metaData) AND isDefined("metaData.type") AND metaData.type EQ "component">
<cfelse>
<cfset session.varSnapShot[ListGetAt(loopList,i)]= duplicate(variables[ListGetAt(loopList,i)])>
</cfif>
</cfloop>
This does make a deep copy but will still be a problem if a component is below the first level of an object. I wanted to create a recursive method, but It's an hour and a half past quitting time on a Friday. I will instead kill brain cells at the pub and maybe update this with the recursive method on Monday if I don't forget.
a modern update to this question:
why not just use #serializeJSON(deserializeJson(theObject))#
that way you get deep copy with no components.