querystring.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // Query String Utilities
  2. // Taken from Jack
  3. var DEFAULT_SEP = "&";
  4. var DEFAULT_EQ = "=";
  5. exports.unescape = function (str, decodeSpaces) {
  6. return decodeURIComponent(decodeSpaces ? str.replace(/\+/g, " ") : str);
  7. };
  8. exports.escape = function (str) {
  9. return encodeURIComponent(str);
  10. };
  11. exports.toQueryString = function (obj, sep, eq, name) {
  12. sep = sep || DEFAULT_SEP;
  13. eq = eq || DEFAULT_EQ;
  14. if (isA(obj, null) || isA(obj, undefined)) {
  15. return name ? encodeURIComponent(name) + eq : '';
  16. }
  17. if (isNumber(obj) || isString(obj)) {
  18. return encodeURIComponent(name) + eq + encodeURIComponent(obj);
  19. }
  20. if (isA(obj, [])) {
  21. var s = [];
  22. name = name+'[]';
  23. for (var i = 0, l = obj.length; i < l; i ++) {
  24. s.push( exports.toQueryString(obj[i], sep, eq, name) );
  25. }
  26. return s.join(sep);
  27. }
  28. // now we know it's an object.
  29. var s = [];
  30. var begin = name ? name + '[' : '';
  31. var end = name ? ']' : '';
  32. for (var i in obj) if (obj.hasOwnProperty(i)) {
  33. var n = begin + i + end;
  34. s.push(exports.toQueryString(obj[i], sep, eq, n));
  35. }
  36. return s.join(sep);
  37. };
  38. exports.parseQuery = function(qs, sep, eq) {
  39. return qs
  40. .split(sep||DEFAULT_SEP)
  41. .map(pieceParser(eq||DEFAULT_EQ))
  42. .reduce(mergeParams);
  43. };
  44. // Parse a key=val string.
  45. // These can get pretty hairy
  46. // example flow:
  47. // parse(foo[bar][][bla]=baz)
  48. // return parse(foo[bar][][bla],"baz")
  49. // return parse(foo[bar][], {bla : "baz"})
  50. // return parse(foo[bar], [{bla:"baz"}])
  51. // return parse(foo, {bar:[{bla:"baz"}]})
  52. // return {foo:{bar:[{bla:"baz"}]}}
  53. var pieceParser = function (eq) {
  54. return function parsePiece (key, val) {
  55. if (arguments.length !== 2) {
  56. // key=val, called from the map/reduce
  57. key = key.split(eq);
  58. return parsePiece(
  59. exports.unescape(key.shift(), true), exports.unescape(key.join(eq), true)
  60. );
  61. }
  62. key = key.replace(/^\s+|\s+$/g, '');
  63. if (isString(val)) val = val.replace(/^\s+|\s+$/g, '');
  64. var sliced = /(.*)\[([^\]]*)\]$/.exec(key);
  65. if (!sliced) {
  66. var ret = {};
  67. if (key) ret[key] = val;
  68. return ret;
  69. }
  70. // ["foo[][bar][][baz]", "foo[][bar][]", "baz"]
  71. var tail = sliced[2], head = sliced[1];
  72. // array: key[]=val
  73. if (!tail) return parsePiece(head, [val]);
  74. // obj: key[subkey]=val
  75. var ret = {};
  76. ret[tail] = val;
  77. return parsePiece(head, ret);
  78. };
  79. };
  80. // the reducer function that merges each query piece together into one set of params
  81. function mergeParams (params, addition) {
  82. return (
  83. // if it's uncontested, then just return the addition.
  84. (!params) ? addition
  85. // if the existing value is an array, then concat it.
  86. : (isA(params, [])) ? params.concat(addition)
  87. // if the existing value is not an array, arrayify it.
  88. : (!isA(params, {}) || !isA(addition, {})) ? [params].concat(addition)
  89. // else merge them as objects, which is a little more complex
  90. : mergeObjects(params, addition)
  91. )
  92. };
  93. // Merge two *objects* together. If this is called, we've already ruled
  94. // out the simple cases, and need to do the for-in business.
  95. function mergeObjects (params, addition) {
  96. for (var i in addition) if (i && addition.hasOwnProperty(i)) {
  97. params[i] = mergeParams(params[i], addition[i]);
  98. }
  99. return params;
  100. };
  101. // duck typing
  102. function isA (thing, canon) {
  103. return (
  104. // truthiness. you can feel it in your gut.
  105. (!thing === !canon)
  106. // typeof is usually "object"
  107. && typeof(thing) === typeof(canon)
  108. // check the constructor
  109. && Object.prototype.toString.call(thing) === Object.prototype.toString.call(canon)
  110. );
  111. };
  112. function isNumber (thing) {
  113. return typeof(thing) === "number" && isFinite(thing);
  114. };
  115. function isString (thing) {
  116. return typeof(thing) === "string";
  117. };