在Javascript中将对象字面量编码为URL查询字符串

3

我正在尝试在一个项目中摆脱jQuery的依赖。该项目的一件事是像这样向服务器发布数据:

var data = {"apple": [{"kiwi": "orange"}, {"banana": "lemon"}], "pear": "passion fruit"};
$.post( url, data);

感谢You might not need jQuery,我知道如何使用XMLHttpRequest在纯Javascript中重写$.post

var request = new XMLHttpRequest();
request.open( 'POST', url, true);
request.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.send( data);

不幸的是,这个描述似乎假定data对象已经是一个URL编码的查询字符串,但在上面的例子中显然并非如此。事实证明,jQuery做了更多的工作:对于给定的data对象,上述$.post调用首先会将其转换为查询字符串,看起来像这样:

apple%5B0%5D%5Bkiwi%5D=orange&apple%5B1%5D%5Bbanana%5D=lemon&pear=passion+fruit

使用XMLHttpRequest的代码片段没有这样做,因此服务器会向我抛出错误。

jQuery还有一个很棒的方法调用$.param,可以完全完成这个转换。如果在上面的XMLHttpRequest代码片段中,最后一行加入该方法,它将工作得非常好。

request.send( $.param(data));

但是,我没有摆脱 jQuery 的依赖。因此,我正在寻找一个纯 Javascript 等价的$.param。有人有类似的东西吗?
注意:问题Plain Javascript Equivalent of jQuery.param()问了一个类似的问题,但接受的答案只适用于非常简单的情况。将该答案中给出的函数应用于上述data对象会产生以下结果:
apple=%5Bobject%20Object%5D%2C%5Bobject%20Object%5D&pear=passion%20fruit

这与上面给出的$.param(data)显然不同,它不会递归地工作,因此会丢失信息。


1
你尝试过仅提取jquery.param的源代码吗?http://james.padolsey.com/jquery/ - Ricardo Castañeda
@RicardoCastañeda 那可能是最好的想法。我希望有一个更直接的解决方案。从jQuery中提取的代码将是完美的答案,我会立即接受它;-) (这也是You might not need jQuery的一个很好的补充)。我猜我会看一下。 - Malte Skoruppa
我已经添加了一份带有代码的答案,如果那不是正确的来源,您可以编辑该帖子。 - Ricardo Castañeda
3个回答

4
我为您制作了一个快速函数,可以帮助您实现此目的。它将从您的键=>值对中创建参数,并将非原始值字符串化。
var objToParams = function(obj){
    var paramString = '';
    for (var key in data) {
        var value = obj[key];
        if(obj[key] instanceof Array || obj[key] instanceof Object){
            value = encodeURIComponent(JSON.stringify(value));
        }
        if (paramString != "") paramString += "&";
        paramString += key + "=" + encodeURIComponent(value);
    }
    return paramString;
}

var data = {"apple": [{"kiwi": "orange"}, {"banana": "lemon"}], "pear": "passion fruit"};
console.log(objToParams(data));

http://jsfiddle.net/7buy3rjy/

编辑,根据您的评论,这应该有效,并且现在与$.param的输出匹配:

http://jsfiddle.net/mg511z7w/

var data = {"apple": [{"kiwi": "orange"}, {"banana": "lemon"}], "pear": "passion fruit"};

var stringifyParam = function(data, topLevel, keyProp) {
        var string = '';
        for (var key in data) {
            if(keyProp && topLevel[keyProp] ) {
                if ( (topLevel[keyProp] instanceof Array&&topLevel[keyProp].indexOf(data[key])!==0) ) {
                    string += keyProp;
                } else if ( (topLevel[keyProp] instanceof Object&&topLevel[keyProp][key]) ) {
                    string += keyProp;
                }
            }
            if (typeof(topLevel[key])=='undefined') {
                string += '[' + key + ']';
            }
            if (data[key] instanceof Array) {
                string += stringifyParam(data[key], topLevel, key);
            } else if(data[key] instanceof Object){
                string += stringifyParam(data[key], topLevel, key);            
            } else {
                if (typeof(topLevel[key])!='undefined') {
                    string += key;
                }
                string += '=' + data[key];
                string += '&';
            }
        }
        return string;
    },
    toParam = function(data){
        var string = stringifyParam(data,data);
        return encodeURI(string.substring(0,string.length-1).split(' ').join('+'));
    };

console.log(toParam(data)); //apple%5B0%5D%5Bkiwi%5D=orange&apple%5B1%5D%5Bbanana%5D=lemon&pear=passion+fruit
console.log($.param(data)); //apple%5B0%5D%5Bkiwi%5D=orange&apple%5B1%5D%5Bbanana%5D=lemon&pear=passion+fruit

谢谢Simon,但这似乎不完全是正确的。如果你在你的小样本最后添加console.log($.param(data));(需要jQuery),你会看到$.param的输出是不同的。我尝试了你的代码,但不幸的是,如果我向服务器发送你的函数的输出,它会出错。 - Malte Skoruppa
喜欢你的方法!代码写得非常好。我只是有点懒,把我的初始答案弄成了我认为想要的样子。 :P - Todd
1
stringifyParam的实现中存在一个bug: 'if (topLevel[key]) { string += key; }' 应该替换为 'if (topLevel[key] != undefined) { string += key; }'因为0(零)也是一个值! :) - Sergio Rykov
恐怕还有另一个错误:调用 toParam({var:{foo:"bar",baz:"qux"}}); 会得到 var%5Bfoo%5D=bar&%5Bbaz%5D=qux,解码后为 var[foo]=bar&[baz]=qux,即第二个 var[] 部分丢失。 - ᴍᴇʜᴏᴠ
我的上一次编辑被拒绝了。重新提交:修复了以下错误:1. 如果传递的数组是{foo:null},它返回了[foo],而应该是foo=(这似乎是@SergioRykov提到的一个错误);2. 如果数据包含多级对象,则不会重复顶级对象键。 - ᴍᴇʜᴏᴠ
显示剩余2条评论

0

你可以使用递归代码来实现,但为什么不尝试一下简单的 JSON 方案呢?这也是它被创建的原因 - 以更简单的方式在客户端和服务器之间交换数据。

只需这样做:

request.send(JSON.stringify(data));

JSON.stringify 接受一个对象,然后将其转换为有效的 JSON,可以在服务器端解析。

要了解有关 JSON 的更多信息,最好的方法莫过于查看其标签摘要 here


感谢您的回答。我知道JSON,但不幸的是,我可以自由修改客户端代码,但无法修改服务器端代码。服务器期望一个查询字符串而不是JSON字符串,因此我需要一个将对象转换为查询字符串的函数,就像$.param一样。 - Malte Skoruppa

0
你可以使用编码和解码URIComponent函数来完成这个任务。

编辑

这个怎么样:

var qs = Object.keys(obj).reduce(function(a,k){
    a.push(k+'='+encodeURIComponent(JSON.stringify(obj[k])));
    return a;
},[]).join('&');

// "apple=%5B%7B%22kiwi%22%3A%22orange%22%7D%2C%7B%22banana%22%3A%22lemon%22%7D%5D&pear=%22passion%20fruit%22"

使用这个代替:

var obj = {"apple": [{"kiwi": "orange"}, {"banana": "lemon"}], "pear": "passion fruit"};

var data = encodeURIComponent(JSON.stringify(obj));

// "%7B%22apple%22%3A%5B%7B%22kiwi%22%3A%22orange%22%7D%2C%7B%22banana%22%3A%22lemon%22%7D%5D%2C%22pear%22%3A%22passion%20fruit%22%7D"

var obj2 = JSON.parse(decodeURIComponent(data));

// {"apple":[{"kiwi":"orange"},{"banana":"lemon"}],"pear":"passion fruit"}

关于您的编辑:它仍然不同于$.param(data)。如上所述,预期输出应为apple%5B0%5D%5Bkiwi%5D=orange&apple%5B1%5D%5Bbanana%5D=lemon&pear=passion+fruit(在URL解码形式下为apple[0][kiwi]=orange&apple[1][banana]=lemon&pear=passion fruit)。您的函数或多或少地对JSON字符串进行了URL编码,这是不同的。 - Malte Skoruppa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接