Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass STRUCT - OR - JSON to Coldfusion CFC Method

I have an existing CFC that works fine when passing structures into the method.

The problem is, we also now need to pass data into the same function via JSON.

Here is the CFC snippet:

<cffunction 
  name="subscribeAPI" 
  access="remote" 
  returntype="struct" 
  returnformat="json" 
  output="false">

  <cfargument 
    name="structure" 
    type="struct" 
    required="true" 
    hint="data structure received from call">

<cfif StructKeyExists(arguments.structure, "listID") 
  AND len(arguments.structure.listID)>
 ...
</cfif>

<cfreturn LOCAL />

Here is how we pass in the structure:

<cfset preStruct = {
  apiAction="Create",
  listID="1463",
  email="#form.cartEmail#",
  firstname="#form.first_name#",
  preCart="#now()#",
  planDescription="#application.name.site#"
  }
/>

<cfscript>voidReturn = application.goxObj.subscribeAPI(preStruct);</cfscript>

Now, we also need to pass in the following but are obviously getting errors due to the CFC expecting a structure:

function HandleSubscribe(){
  $j.getJSON(
    "/com/list.cfc?wsdl",
    {
      method : "subscribeAPI",
      action : "Create",
      listID : $j( "#listID" ).val(),
      triggerKey : $j( "#triggerKey" ).val(),
      email : $j( "#emailNL" ).val(),
      firstname : $j( "#firstnameNL" ).val()
    },
  handleSubscribeCallback
);

}

How can we successfully pass in the getJSON snippet?

Thanks.

like image 875
goxmedia Avatar asked Feb 28 '12 20:02

goxmedia


Video Answer


3 Answers

JSON is just a string, so you need to "handle" the method call before it reaches your actual service layer.

Danimal is right in that what you need to do is create a web service layer wrapper around your service.

So your service method looks like this :

<cffunction name="CreateSubscription" access="public" returntype="struct" output="false">
    <cfargument name="listID" required="true" type="numeric">
    <cfargument name="emailaddress" required="true" type="string">
    <cfargument name="firstname" required="true" type="string">

    <cfset var resultset = {success=false}>

    <!--- Validate your listid and subscription details --->
    <!--- If Valid Then insert subscription --->
    <cfset resultset.success = true>

    <!--- else --->
    <cfset resultset.message = 'kerboom!'>

    <!--- only return what you need as a struct, not the whole local scope! --->
    <cfreturn resultset />
 </cffunction>

Your subscription API looks like this :

<cffunction name="subscribeAPI" access="remote" returntype="struct" returnformat="json" output="false">

   <cfargument name="JSONPacket" type="string" required="true" hint="data structure received from call">
   <cfset var incomingData = deserializeJSON(arguments.JSONPacket)>
   <cfset var resultset = {success=false,message='invalid data'}>

   <cfif StructKeyExists(incomingData, "apiAction")>
       <cfif incomingData.apiAction EQ "create">
           <!--- You should also check you have the required fields for the createSubscription method here too. --->
           <cfset resultset = subscriptionService.createSubscription(incomingData)>
       </cfif>
   <cfelse>
       <cfset resultset.message = 'No API Action specified'>
   </cfif> 

   <cfreturn resultset>
</cffunction>

So you push the JSON at the subscribe API, which converts the data to a struct and makes sure you have all the right data available and passes it off to your subscription service. The createSubscription method in the subscription service checks to see if the listid exists and checks to see if the person is already subscribed. If the list is good and the subscription doesn't exist, insert the new subscription into the database, otherwise return results that indicate what went wrong in a struct to your API layer, which converts it to JSON and returns it.

The benefit to this is you can re-use the services in your application without having to go through the API layer and your api layer handles pushing the requests to the correct service methods and making sure that there is appropriate data available for them.

Don't be passing the local scope around! There can be a shed load of stuff in there including all the other methods in the service. Just return what is required and nothing more.

There are other ways you can solve this that might be neater - for example you can actually put arguments into a method method call on a CFC from JSON. You could use cfajaxproxy to create the layer between your service and your javascript, enabling you to call your cfc methods directly as javascript functions. And I'm sure there are other solutions on top of these.

Remember.... ColdFusion == Serverside, Javascript == clientside. Separate them. Put a layer between them to handle communications.

Hope that helps.

like image 65
Stephen Moretti Avatar answered Oct 23 '22 04:10

Stephen Moretti


If you would like a method to take either a struct or a json string as a single argument, you can do something like...

<cffunction name="myFunction" access="remote" returntype="Struct" returnformat="JSON">
    <cfargument name="data" type="any" required="true">

    <cfif isJson(arguments.data)>
        <cfset arguments.data = deserializeJSON(arguments.data) />
    </cfif>

    <cfif NOT isStruct(arguments.data)>
        <cfthrow message="argument must be structure or a json string" />
    </cfif>

    ... 

</cffunction>
like image 43
Damon Warren Avatar answered Oct 23 '22 05:10

Damon Warren


Edited from original solution:

It doesn't look like you can pass a JSON object directly to Coldfusion and have it interpreted as a Struct. What you CAN do is to create a wrapper method around your existing method that accepts a JSON string, then deserializes it to a struct in Coldfusion to pass in to your existing method:

<script>
    var data = { dude: "wow"};
    $(function() {
        $('#ajax').click(function() {
            $.getJSON(
                "test.cfc", 
                {
                    method: "foo",
                    json: JSON.stringify(data)
                },
                function(data) {
                    // so something with result
                }   
            );
        });
    })
</script>

And the Coldfusion:

<cffunction name="foo" access="remote" returntype="Struct" returnformat="JSON">
    <cfargument name="json" type="string" />

        <cfset myStruct = DeserializeJSON(arguments.json) />

        <!--- now call your existing method passing it myStruct --->
</cffunction>
like image 25
Dan A. Avatar answered Oct 23 '22 03:10

Dan A.