15

我确信我完全把这件事搞砸了,但在 Stack Overflow 用户的帮助下,我走到了这一步,非常感谢。

我需要将 JSON 数据发布到远程 API。显然由于 SOP 问题我不能使用 jQuery,并且远程 API 不支持 JSONP。

我也不想使用任何类型的代理来绕过 SOP 限制。

根据 API 文档 ( http://myemma.com/api-docs/ ),这是他们期望的数据格式(请求和响应数据以 JSON 格式传输):

POST https://api.e2ma.net//123/members/add
{
  "fields": {
    "first_name": "myFirstName"
  }, 
  "email": "email@domain.com"
}

这就是我迄今为止构建的,但继续从远程 API 收到“无法解析 JSON”错误:

<cfset fields[name_first]="#SerializeJSON( "myFirstName" )#" />
<cfset form.email="#SerializeJSON( "email@domain.com" )#" />

<cfhttp
  url="https://api.e2ma.net/123/members/add"
  method="POST"
  username="username"
  password="pssword"
  useragent="#CGI.http_user_agent#"
  result="objGet">

  <!--- add email --->
  <cfhttpparam
    type="formfield"
    name="email"
    value='#form.email#'
  />

  <!--- add field: name_first --->
  <cfhttpparam
    type="formfield"
    name="fields"
    value='#fields[name_first]#'
  />

</cfhttp>

<cfoutput>#objGet.FileContent#</cfoutput>

同样,我肯定会以某种方式破坏我的数据结构,但我不确定我做错了什么,特别是关于正确设置“字段”:{“first_name”:“myFirstName”}结构/数组。

4

5 回答 5

29

您应该将请求字符串作为 httpparam 类型的正文发送。请求的主体可能类似于您准备好的结构的整个表单范围。确保使用数组表示法来设置结构键或在隐式结构创建期间将它们放在“引号”中,以确保它们在发生 serializeJSON() 时保持正确的大小写,否则 ColdFusion 会将结构键大写。

<cfset stFields = {
    "fields" = {
        "first_name" = "myFirstName"
     }, 
     "email" = "email@domain.com"
}>   

<cfhttp url="http://api.url.com" method="post" result="httpResp" timeout="60">
    <cfhttpparam type="header" name="Content-Type" value="application/json" />
    <cfhttpparam type="body" value="#serializeJSON(stFields)#">
</cfhttp>

2013 年 10 月 26 日更新
对于我最近使用 API 所做的所有工作,我想我会更新一种简单的方法来自动化我发现的这个外壳。我结合使用JSON Util库和 Ben Nadel 的JSON Serializer Utility CFC来为所有返回实现更好的序列化一致性。

下面是我如何实现这一点的示例 GIST。
https://gist.github.com/timmaybrown/7226809

当我过渡到在我的项目中使用持久实体 CFC 时,我发现使用我自己的子 CFC 方法扩展 Ben Nadel 的序列化程序 CFC,该方法使用getComponentMetaData()函数循环我的所有持久 cfc 的属性以构建不同键的结构以及后续序列化的外壳。该方法允许我的 api 在我的实体中自动继承我的属性名称的大小写,并且非常有用。reinit 有一点开销,但非常值得让你的外壳在你的 API 中保持一致。

更新 2016 年 9 月 8 日 回复:我上面关于一致外壳的观点。对于较新的项目,我倾向于在我的数据库中使用不同的列命名约定,因此我不必处理很多这些问题。first_name而不是firstName

于 2012-02-04T17:44:45.837 回答
10

更新:2012 年 9 月 26 日:在使用我设置的演示帐户请求 API 密钥后,他们向我发送了一个可能的帐户 ID。我将代码放在下面,它就像添加成员的魅力。

首先让我说这些代码都没有经过测试 (参见上面的更新)。我没有 MyEmma 帐户,显然您必须成为 account_id 的付费客户才能使用 API。那吹!但这应该让你真正接近,并且可能会给你一些封装逻辑的想法,这已经成为我的痴迷。

其次,我意识到这篇文章已经 9 个月大了,你可能早就想通了,或者中了彩票并且现在正在运行这个地方。所以没有人甚至可能会看到这篇文章。但是我自己在寻找一些答案并遇到了它......并且由于制定和解析 JSON 是我日常生活的一部分,所以我总是需要继续努力。所以结果是对你问题的快速回答,变成了一个深夜、自我服务、强迫性的挑战。好歹...

...您使用 JSON 所做的是创建客户端嵌套结构。您拥有包含两个键值对(字段和电子邮件)的根结构。然后结构“字段”包含一个结构,其中包含您为该电子邮件地址(first_name)发送的键值对。大概你可以发送更多。

您正在构建嵌套结构。请记住,结构中的键可以保存结构。这些键可以保存结构,等等。它可以变得像你想要的那样黑暗和讨厌。但这就是 JSON 的全部内容......它是一个客户端对象。

所以这是您的数据构建和 JSON 对象...

<cfscript>
    variables.dataFields = {};
    variables.dataFields['fields'] = {};
    variables.dataFields['email'] = "email@domain.com";
    variables.dataFields.fields['first_name'] = "myFirstName";
    variables.dataFields = serializejson(variables.dataFields);
</cfscript>

请注意,我使用数组表示法显式设置结构键名称。我们必须这样做以控制 Coldfusion 的情况。否则,键将全部大写......不希望我们想要区分大小写的 JavaScript。这可能是您遇到的问题的一部分。

如果艾玛因为案件不明白,那么你会得到你的......

{"error": "Unable to parse JSON request"}

但是当我们使用数组表示法显式设置我们的键名,然后序列化我们的对象时,我们会得到漂亮、漂亮、时尚的 JSON...

{"fields":{"first_name":"myFirstName"},"email":"email@domain.com"}

所以下面,我把我们对 Emma 的 http 请求放在一个函数中。将 Content-Type 标头设置为 application/json也很重要,因此浏览器会将其作为对象发送,而不仅仅是文本字符串。我们将 JSON 作为请求的主体发送,而不是在名为“字段”的表单字段中发送……希望当你大声说出来时这很有意义。这是功能...

<cffunction name="callEmma" access="private" displayname="CallEmma" description="This makes an HTTP REQUEST to MyEmma" returnformat="JSON" output="false" returntype="Any">
    <cfargument name="endpoint" required="true" type="string" displayname="EndPoint">
    <cfargument name="PUBLIC_API_KEY" required="true" type="string" displayname="PUBLIC_API_KEY">
    <cfargument name="PRIVATE_API_KEY" required="true" type="string" displayname="PRIVATE_API_KEY">
    <cfargument name="dataFields" required="true" type="struct" displayname="DataFields">
    <cfscript>
        local = {};
        local.baseURL = "https://api.e2ma.net/";
        local.account_id = "12345";
        local.phoneNumber = local.baseURL & local.account_id & arguments.endPoint;
        local.connection = new http();
        local.connection.setMethod("POST"); 
        local.connection.setUrl(local.phoneNumber);
        local.connection.setUsername(arguments.PUBLIC_API_KEY);
        local.connection.setPassword(arguments.PRIVATE_API_KEY);
        local.connection.setUserAgent(cgi.http_user_agent);
        local.connection.addParam(type="header",name="Content-Type", value="application/json");
        local.connection.addParam(type="body", value=arguments.dataFields); 
        local.objGet = local.connection.send().getPrefix();
        local.content = local.objGet.filecontent;
        return local.content
    </cfscript>
</cffunction>

然后再一次,这是我们的 JSON 构建(嵌套结构)......

<cfscript>
    variables.dataFields = {};
    variables.dataFields['fields'] = {};
    variables.dataFields['email'] = "email@domain.com";
    variables.dataFields.fields['first_name'] = "myFirstName";
    variables.dataFields = serializejson(variables.dataFields);
</cfscript>

然后我们设置要传递给函数的变量...

<cfscript>
    variables.entryPoint = "/members/add";
    variables.PUBLIC_API_KEY= "PUBLIC_API_KEY";
    variables.PRIVATE_API_KEY= "PRIVATE_API_KEY";
</cfscript>

然后打电话...

<cfscript>
    variables.myResponse = callEmma(variables.entryPoint,variables.PUBLIC_API_KEY,variables.PRIVATE_API_KEY,variables.dataFields);
    variables.myResponse = deserializejson(variables.myResponse);
</cfscript>

然后我们接受我们的响应,反序列化它,并输出我们想要的变量。

<cfscript>
    if(variables.myResponse.added){
        writeoutput("Member " & variables.myResponse.member_id & " added!");
    }
    else{
        writeoutput("There was an error adding this member");
    }
</cfscript>

再说了,我通常<cfscript>尽可能多地使用。它更容易阅读,它让我感觉自己比实际聪明得多。所以当我们把它们放在一起,为了剪切和粘贴,我们有这个......

<cfscript>
// Function to make our calls to Emma
private any function callEmma(required string endPoint,required string PUBLIC_API_KEY,required string PRIVATE_API_KEY,required string dataFields)
    description="This makes an HTTP REQUEST to MyEmma"
    displayname="CallEmma"
    returnformat="JSON"
    output="false"
{
    local = {};
    local.baseURL = "https://api.e2ma.net/";
    local.account_id = "12345";
    local.phoneNumber = local.baseURL & local.account_id & arguments.endPoint;
    local.connection = new http();
    local.connection.setMethod("POST"); 
    local.connection.setUrl(local.phoneNumber);
    local.connection.setUsername(arguments.PUBLIC_API_KEY);
    local.connection.setPassword(arguments.PRIVATE_API_KEY);
    local.connection.setUserAgent(cgi.http_user_agent);
    local.connection.addParam(type="header",name="Content-Type", value="application/json");
    local.connection.addParam(type="body",value=arguments.dataFields); 
    local.objGet = local.connection.send().getPrefix();
    local.content = local.objGet.filecontent;
    return local.content;
} 

// Put our data together
variables.dataFields = {};
variables.dataFields['fields'] = {};
variables.dataFields['email'] = "email@domain.com";
variables.dataFields.fields['first_name'] = "myFirstName";
variables.dataFields = serializejson(variables.dataFields);

// Define the parameters for our call to Emma
variables.entryPoint = "/members/add";
variables.PUBLIC_API_KEY= "PUBLIC_API_KEY";
variables.PRIVATE_API_KEY= "PRIVATE_API_KEY";

// Call Emma
variables.myResponse = callEmma(variables.entryPoint,variables.PUBLIC_API_KEY,variables.PRIVATE_API_KEY,variables.dataFields);
variables.myResponse = deserializejson(variables.myResponse);

//Output to browser
if(variables.myResponse.added){
    writeoutput("Member " & variables.myResponse.member_id & " added!");
}
else{
    writeoutput("There was an error adding this member");
}
</cfscript>

我的上帝!我一直在写太多的 API ......我显然需要治疗!

于 2012-09-25T07:55:09.943 回答
1

你提到的结构

{ "fields": { "first_name": "myFirstName" }, "email": "email@domain.com" } 在这个 JSON 中,'fields' 的键值又是一个 JSON 所以,你可以这样:

<cfscript>
        VARIABLES.postJSON = StructNew();
        VARIABLES.nameJSON = StructNew();
        StructInsert(VARIABLES.nameJSON, 'first_name','myFirstName');
        StructInsert(VARIABLES.postJSON, 'fields',VARIABLES.nameJSON);
        StructInsert(VARIABLES.postJSON, 'email','email@domain.com');
        
</cfscript> 

<cfhttp
  url="https://api.e2ma.net/123/members/add"
  method="POST"
  username="username"
  password="pssword"
  useragent="#CGI.http_user_agent#"
  result="objGet">

  <cfhttpparam
    type="body"
    name="field"
    value='#SerializeJSON(VARIABLES.postJSON)#'
  />

</cfhttp>

<cfoutput>#objGet.FileContent#</cfoutput>
于 2014-08-26T11:36:54.570 回答
0

鉴于您提交数据的方式,您不必序列化字符串,只需

value='#serializejson(fields)#'

从您的评论来看,这对您不起作用。不幸的是,他们的文档在如何发送数据方面让 IMO 感到困惑。他们说它应该是一个帖子,但只显示一个 json 对象。如果从 JS 中使用,也许这很有用,但否则会令人困惑。

要缩小问题发生的范围,请尝试静态提交信息,例如获取他们的示例代码并粘贴到字段的值中。您应该首先尝试在动态版本之前进行静态尝试。甚至可能由于区分大小写或其他问题,CF json 序列化会出错。

<!--- add email --->
<cfhttpparam
  type="formfield"
  name="email"
  value='email@domain.com'
/>

<!--- add field: name_first --->
<cfhttpparam
  type="formfield"
  name="fields"
  value='{ "first_name": "myFirstName" }'
/>
<!--- or if that doesn't work also try value='"first_name": "myFirstName" ' --->
于 2012-01-20T01:11:44.253 回答
0

恰逢其时。因为我们目前正在解决同样的问题。

我们目前正在努力将我们的 CF 版本从 8 更新到 9.01,并且有一些使用 cfajaxproxy 的代码 - 在 9.01 下无法运行 - 但在 CF8 中运行良好。

我不确定 (1) 问题的实际根本原因是什么;如果我有时间,我会做一些更具体的工作......但解决方法是将通过 ajax 调用的代码放在 webroot 中。

(1) 它可能是由使用虚拟目录引起的,或者可能是受到 CF 应用程序框架的影响 - CFIDE 脚本自动插入到文件中 - 并与返回的 JSON 的预期格式相混淆。

我用 Adob​​e 记录了一个错误。

于 2012-01-20T01:17:58.263 回答