There are at least 2×5×4×3×3×1=360 different things you might unambiguously mean by "parsing" a query string in Javascript:
- What data type do you want returned? (Below code provides both these)
Map
Object
- How do you want duplicate keys handled? (Below code provides all these)
- Just the first item (like built-in
URLSearchParams
) - Just the last item (like PHP's
parse_str
; below code prefers this) - All the items, as an
Array
(like Python'sparse_qs
) - All the items, as a
Set
- Throw an error
- Just the first item (like built-in
- How do you want instances of
…&=val&…
(empty key name) handled?- Take the key to be the empty string (like built-in
URLSearchParams
; like Python'sparse_qs
; below code chooses this) - Ignore them entirely (like PHP's
parse_str
) - Throw an error
- Take the key to be the empty string (like built-in
- How do you want instances of
…&key=&…
(empty value) handled?- Take the value to be the empty string (like built-in
URLSearchParams
; below code chooses this) - Ignore them entirely (like Python's
parse_qs
) - Reset the key; treat them as a
delete
directive - Throw an error
- Take the value to be the empty string (like built-in
- How do you want instances of
…&key&…
(missing an equals-sign) handled?- Take the key to be present but the value absent, by a constant such as
null
(below code chooses this) - Pretend there's an equals-sign; treat them identically to an empty value (like built-in
URLSearchParams
; like Python'sparse_qs
) - Ignore them entirely, despite empty values being treated as the empty string
- Reset the key; treat them as a
delete
directive - Throw an error
- Take the key to be present but the value absent, by a constant such as
- Do you want further parsing done on the values within this function?
The below code demos some of the most important and useful combinations of these:
function _parse_qs_array(search) {
if(search === undefined) search = window.location.search;
let m = search.match(/^\?(.+)$/s);
let params = m ? m[1].split('&') : new Array();
return params.map(s => {
let [key, value] = s.match(/^(.*?)(?:=(.*))?$/s).slice(1);
key = decodeURIComponent(key.replaceAll('+', '%20'));
value = (value !== undefined ) ? decodeURIComponent(value.replaceAll('+', '%20')) : null;
return [key, value];
});
}
function parse_qs_map(search) {
// Parses the given query string, returning the results as a Map.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys take on the LAST value provided (like PHP's parse_str).
return new Map(_parse_qs_array(search));
}
function parse_qs_obj(search) {
// Parses the given query string, returning the results as an Object.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys take on the LAST value provided (like PHP's parse_str).
return Object.fromEntries(_parse_qs_array(search));
}
function parse_qs_map_first(search) {
// Parses the given query string, returning the results as a Map.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys take on the FIRST value provided (like URLSearchParams).
return new Map(_parse_qs_array(search).reverse());
}
function parse_qs_obj_first(search) {
// Parses the given query string, returning the results as an Object.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys take on the FIRST value provided (like URLSearchParams).
return Object.fromEntries(_parse_qs_array(search).reverse());
}
function parse_qs_map_all(search) {
// Parses the given query string, returning the results as a Map with Array values.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys are all returned, in-order.
const map = new Map();
_parse_qs_array(search).forEach(([key, value]) => {
if(! map.has(key) ) map.set(key, new Array());
map.get(key).push(value);
});
return map;
}
function parse_qs_obj_all(search) {
// Parses the given query string, returning the results as an Object with Array values.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys are all returned, in-order.
const obj = new Object();
_parse_qs_array(search).forEach(([key, value]) => {
if( obj[key] === undefined ) obj[key] = new Array();
obj[key].push(value);
});
return obj;
}
function parse_qs_map_all_unique(search) {
// Parses the given query string, returning the results as a Map with Set values.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys are all returned. (Duplicate key-value pairs have no effect.)
const map = new Map();
_parse_qs_array(search).forEach(([key, value]) => {
if(! map.has(key) ) map.set(key, new Set());
map.get(key).add(value);
});
return map;
}
function parse_qs_obj_all_unique(search) {
// Parses the given query string, returning the results as an Object with Set values.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys are all returned. (Duplicate key-value pairs have no effect.)
const obj = new Object();
_parse_qs_array(search).forEach(([key, value]) => {
if( obj[key] === undefined ) obj[key] = new Set();
obj[key].add(value);
});
return obj;
}
function parse_qs_map_1(search) {
// Parses the given query string, returning the results as a Map.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys will cause this function to throw a SyntaxError.
const map = new Map();
_parse_qs_array(search).forEach(([key, value]) => {
if( map.has(key) ) throw new SyntaxError(`${arguments.callee.name}: duplicate key`);
map.set(key, value);
});
return map;
}
function parse_qs_obj_1(search) {
// Parses the given query string, returning the results as an Object.
// Naked keys (without an equals-sign) are taken to have null values.
// Duplicate keys will cause this function to throw a SyntaxError.
const obj = new Object();
_parse_qs_array(search).forEach(([key, value]) => {
if( obj[key] !== undefined ) throw new SyntaxError(`${arguments.callee.name}: duplicate key`);
obj[key] = value;
});
return obj;
}