174 lines
5.7 KiB
JavaScript
174 lines
5.7 KiB
JavaScript
'use strict';
|
|
|
|
var utils = require('./utils');
|
|
|
|
var has = Object.prototype.hasOwnProperty;
|
|
|
|
var defaults = {
|
|
allowDots: false,
|
|
allowPrototypes: false,
|
|
arrayLimit: 20,
|
|
decoder: utils.decode,
|
|
delimiter: '&',
|
|
depth: 5,
|
|
parameterLimit: 1000,
|
|
plainObjects: false,
|
|
strictNullHandling: false
|
|
};
|
|
|
|
var parseValues = function parseQueryStringValues(str, options) {
|
|
var obj = {};
|
|
var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
|
|
var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
|
|
var parts = cleanStr.split(options.delimiter, limit);
|
|
|
|
for (var i = 0; i < parts.length; ++i) {
|
|
var part = parts[i];
|
|
|
|
var bracketEqualsPos = part.indexOf(']=');
|
|
var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
|
|
|
|
var key, val;
|
|
if (pos === -1) {
|
|
key = options.decoder(part, defaults.decoder);
|
|
val = options.strictNullHandling ? null : '';
|
|
} else {
|
|
key = options.decoder(part.slice(0, pos), defaults.decoder);
|
|
val = options.decoder(part.slice(pos + 1), defaults.decoder);
|
|
}
|
|
if (has.call(obj, key)) {
|
|
obj[key] = [].concat(obj[key]).concat(val);
|
|
} else {
|
|
obj[key] = val;
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
};
|
|
|
|
var parseObject = function (chain, val, options) {
|
|
var leaf = val;
|
|
|
|
for (var i = chain.length - 1; i >= 0; --i) {
|
|
var obj;
|
|
var root = chain[i];
|
|
|
|
if (root === '[]') {
|
|
obj = [];
|
|
obj = obj.concat(leaf);
|
|
} else {
|
|
obj = options.plainObjects ? Object.create(null) : {};
|
|
var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
|
|
var index = parseInt(cleanRoot, 10);
|
|
if (
|
|
!isNaN(index)
|
|
&& root !== cleanRoot
|
|
&& String(index) === cleanRoot
|
|
&& index >= 0
|
|
&& (options.parseArrays && index <= options.arrayLimit)
|
|
) {
|
|
obj = [];
|
|
obj[index] = leaf;
|
|
} else {
|
|
obj[cleanRoot] = leaf;
|
|
}
|
|
}
|
|
|
|
leaf = obj;
|
|
}
|
|
|
|
return leaf;
|
|
};
|
|
|
|
var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
|
|
if (!givenKey) {
|
|
return;
|
|
}
|
|
|
|
// Transform dot notation to bracket notation
|
|
var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey;
|
|
|
|
// The regex chunks
|
|
|
|
var brackets = /(\[[^[\]]*])/;
|
|
var child = /(\[[^[\]]*])/g;
|
|
|
|
// Get the parent
|
|
|
|
var segment = brackets.exec(key);
|
|
var parent = segment ? key.slice(0, segment.index) : key;
|
|
|
|
// Stash the parent if it exists
|
|
|
|
var keys = [];
|
|
if (parent) {
|
|
// If we aren't using plain objects, optionally prefix keys
|
|
// that would overwrite object prototype properties
|
|
if (!options.plainObjects && has.call(Object.prototype, parent)) {
|
|
if (!options.allowPrototypes) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
keys.push(parent);
|
|
}
|
|
|
|
// Loop through children appending to the array until we hit depth
|
|
|
|
var i = 0;
|
|
while ((segment = child.exec(key)) !== null && i < options.depth) {
|
|
i += 1;
|
|
if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {
|
|
if (!options.allowPrototypes) {
|
|
return;
|
|
}
|
|
}
|
|
keys.push(segment[1]);
|
|
}
|
|
|
|
// If there's a remainder, just add whatever is left
|
|
|
|
if (segment) {
|
|
keys.push('[' + key.slice(segment.index) + ']');
|
|
}
|
|
|
|
return parseObject(keys, val, options);
|
|
};
|
|
|
|
module.exports = function (str, opts) {
|
|
var options = opts ? utils.assign({}, opts) : {};
|
|
|
|
if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') {
|
|
throw new TypeError('Decoder has to be a function.');
|
|
}
|
|
|
|
options.ignoreQueryPrefix = options.ignoreQueryPrefix === true;
|
|
options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter;
|
|
options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth;
|
|
options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit;
|
|
options.parseArrays = options.parseArrays !== false;
|
|
options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder;
|
|
options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : defaults.allowDots;
|
|
options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects;
|
|
options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes;
|
|
options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit;
|
|
options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
|
|
|
|
if (str === '' || str === null || typeof str === 'undefined') {
|
|
return options.plainObjects ? Object.create(null) : {};
|
|
}
|
|
|
|
var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
|
|
var obj = options.plainObjects ? Object.create(null) : {};
|
|
|
|
// Iterate over the keys and setup the new object
|
|
|
|
var keys = Object.keys(tempObj);
|
|
for (var i = 0; i < keys.length; ++i) {
|
|
var key = keys[i];
|
|
var newObj = parseKeys(key, tempObj[key], options);
|
|
obj = utils.merge(obj, newObj, options);
|
|
}
|
|
|
|
return utils.compact(obj);
|
|
};
|