Nested Query Parser (qs-style)
Hard · Security
Express's "extended" query parser (the qs package) turns ?user[name]=Ann&tags[]=a&tags[]=b into nested objects and arrays. Write parseQuery(query), a simplified version of it.
Splitting and decoding:
- Remove a leading ?. Split on & and skip empty pieces. Split each piece at the first = into key and value (no = means the value is ""). Skip pieces whose key is empty.
- Decode the key and the value: replace + with a space, then call decodeURIComponent. If that throws, keep the text as it is (after the + replacement).
Keys:
- The root is the text before the first [. If the key has no [, starts with [, or the part after the root is not made only of [...] groups (like a[b]c or a[b), the whole key is one plain key.
- Otherwise each group adds one level of nesting. At most 5 groups are used: if there are more, the remaining text (for example [g][h]) becomes one extra literal key.
- [] appends to an array: it uses the index right after the largest index used so far. [<n>], where n is 0 to 20 written without leading zeros, is an array index. Arrays are compacted at the end: holes disappear and items are ordered by index. Any other group (or a larger index) is an object key; if the container was an array so far, it becomes an object that keeps its indexes as keys.
Values:
- The same key path twice (a=1&a=2, or a[b]=1&a[b]=2) collects the values into an array, and a value assigned to a key that already holds an array is appended.
- If the key already holds an object, or a nested path runs into a string (a=1&a[b]=2), the new pair is ignored.
Security: skip the whole pair if any part of the key is __proto__, constructor or prototype. Otherwise __proto__[isAdmin]=1 could write into Object.prototype, and suddenly every object in the app has isAdmin (this attack is called prototype pollution). Also make sure keys like toString work as normal keys: build your containers with Object.create(null) or check own properties.