views:

1566

answers:

5

Given the following form:

<form>
  <input name="foo" value="bar">
  <input name="hello" value="hello world">
</form>

I can use the $.param( .. ) to serialize the form

$.param( $('form input') )

=> foo=bar&hello=hello+world 

How can I deserialize the above String with javascript and get a Hash back? e.g.

$.magicFunction("foo=bar&hello=hello+world") 

=> {'foo' : 'bar', 'hello' : 'hello world'}

Thanks

Reference: http://docs.jquery.com/Utilities/jQuery.param#obj

+6  A: 

This is a slightly modified version of a function I wrote a while ago to do something similar.

var QueryStringToHash = function QueryStringToHash  (query) {
  var query_string = {};
  var vars = query.split("&");
  for (var i=0;i<vars.length;i++) {
    var pair = vars[i].split("=");
    pair[0] = decodeURIComponent(pair[0]);
    pair[1] = decodeURIComponent(pair[1]);
     // If first entry with this name
    if (typeof query_string[pair[0]] === "undefined") {
      query_string[pair[0]] = pair[1];
     // If second entry with this name
    } else if (typeof query_string[pair[0]] === "string") {
      var arr = [ query_string[pair[0]], pair[1] ];
      query_string[pair[0]] = arr;
     // If third or later entry with this name
    } else {
      query_string[pair[0]].push(pair[1]);
    }
  } 
  return query_string;
};
David Dorward
Exactly what I thought with accepted answer "this won't work with arrays". Your solution works all right.
Robert Koritnik
+3  A: 

Here's how you could create a new jQuery function:

jQuery.unparam = function (value) {
    var
    // Object that holds names => values.
    params = {},
    // Get query string pieces (separated by &)
    pieces = value.split('&'),
    // Temporary variables used in loop.
    pair, i, l;

    // Loop through query string pieces and assign params.
    for (i = 0, l = pieces.length; i < l; i++) {
        pair = pieces[i].split('=', 2);
        // Repeated parameters with the same name are overwritten. Parameters
        // with no value get set to boolean true.
        params[decodeURIComponent(pair[0])] = (pair.length == 2 ?
            decodeURIComponent(pair[1].replace(/\+/g, ' ')) : true);
    }

    return params;
};
Blixt
I don't like this function. Why should parameter with no value should get the value of 'true'? Why not null? I think this design decision can lead to very subtle bugs. Also, why not handle the situation where parameter can have multiple values instead of selecting the 'last' value? Solution posted by David is much better.
SolutionYogi
It is `true` so that one can check `if (params.redirect)` or similar. If you want to differ between `true` and other value, you would use `if (params.redirect === true)`. A `null` value wouldn't help in any way that I can think of. The reason I didn't do it like David is because I value consistency over flexibility. With his method I have to check if the value is a string or an array to use its value. In my opinion, it should always be a string (the `true` value converts to `'true'` which I think is fine), or always be an array. I chose string.
Blixt
By that account, if it was params.delete, it would be set to true and I will do something harmful. As you can see, coming up with example is easy. 'null' clearly specifies that no value exists and the decision is left to the caller what how he wants to interpret it. About multiple values, look at it this way. By eating up, you are making sure that either caller will spend time debugging why he is getting only one value OR later on come and modify the code. By returning the array upfront, caller will immediately know how to handle them. Discarding information is never good, IMHO.
SolutionYogi
If the URL is `/abc?delete` then I would expect there to be an entry for `delete`. I don't see how that is different? I don't even see how you can prefer the other code for this reason, because that code will set the value to the string `'undefined'` which is not better. And as for multiple values it's a case of simplicity vs. flexibility. I rarely see the use of multiple values in query strings, but if you want them, always return an array with 1+ values. Never mix the return type; then you *will* have to spend time to debug because what was usually a string could suddenly be an array.
Blixt
seb
Of course, I just want people to be aware of the dangers of inconsistent magic. It's so overlooked that even the best aren't prepared for it: http://pecl.php.net/package-search.php?pkg_name%5B%5D=arrays Usually it just messes up the result for the user, but it could have other consequences.
Blixt
A: 

Thanks to him http://james.padolsey.com/javascript/parsing-urls-with-the-dom/

Pretty easy :D

function params_unserialize(p){
var ret = {},
    seg = p.replace(/^\?/,'').split('&'),
    len = seg.length, i = 0, s;
for (;i<len;i++) {
    if (!seg[i]) { continue; }
    s = seg[i].split('=');
    ret[s[0]] = s[1];
}
return ret;}
BrutusCat
A: 

Here's my javascript implementation which I use in a server-side JScript classic ASP page:

// transforms a querystring in the form x[y][0][z][]=1 into {x:{y:[{z:[1]}]}}
function ParseJQueryParams(p) {
    var params = {};
    var pairs = p.split('&');
    for (var i=0; i<pairs.length; i++) {
        var pair = pairs[i].split('=');
        var accessors = [];
        var name = decodeURIComponent(pair[0]), value = decodeURIComponent(pair[1]);

        var name = name.replace(/\[([^\]]*)\]/g, function(k, acc) { accessors.push(acc); return ""; });
        accessors.unshift(name);
        var o = params;

        for (var j=0; j<accessors.length-1; j++) {
            var acc = accessors[j];
            var nextAcc = accessors[j+1];
            if (!o[acc]) {
                if ((nextAcc == "") || (/^[0-9]+$/.test(nextAcc))) 
                    o[acc] = [];
                else
                    o[acc] = {};
            }
            o = o[acc];
        }
        acc = accessors[accessors.length-1];
        if (acc == "")
            o.push(value);
        else
            o[acc] = value;             
    }
    return params;
}
Yuval
+1  A: 

I am using David Dorward's answer, and realized that it doesn't behave like PHP or Ruby on Rails how they parse the params:

1) a variable is only an array if it ends with [], such as ?choice[]=1&choice[]=12, not when it is ?a=1&a=2

2) when mulitple params exist with the same name, the later ones replaces the earlier ones, as on PHP servers (Ruby on Rails keep the first one and ignore the later ones), such as ?a=1&b=2&a=3

So modifying David's version, I have:

function QueryStringToHash(query) {

  if (query == '') return null;

  var hash = {};

  var vars = query.split("&");

  for (var i = 0; i < vars.length; i++) {
    var pair = vars[i].split("=");
    var k = decodeURIComponent(pair[0]);
    var v = decodeURIComponent(pair[1]);

    // If it is the first entry with this name
    if (typeof hash[k] === "undefined") {

      if (k.substr(k.length-2) != '[]')  // not end with []. cannot use negative index as IE doesn't understand it
        hash[k] = v;
      else
        hash[k] = [v];

    // If subsequent entry with this name and not array
    } else if (typeof hash[k] === "string") {
      hash[k] = v;  // replace it

    // If subsequent entry with this name and is array
    } else {
      hash[k].push(v);
    }
  } 
  return hash;
};

which is tested fairly thoroughly.

動靜能量