Last 12 weeks · 17 commits
4 of 6 standards met
[Fix] : throw on a non-primitive value in a comma-separated array Fixes #378. Problem with silently coerces a non-primitive array element (a plain object, or a nested array) to the literal string — or silently flattens a nested array — irrecoverably losing the nested data: As the maintainer noted in #378, the current behavior is unusable, and the two cases that exercise it were left skipped with . Root cause In , when is the array is collapsed with : calls on every element. For a plain object that yields ; for a nested array it silently flattens. Either way the structure is lost with no error. Fix Per the maintainer's primary suggestion in #378 ("qs could throw if it encounters a non-primitive when stringifying arrayFormat comma") using his recommended idiom (), detect a non-primitive element and throw a descriptive instead of silently corrupting it. The check is folded into the pre-existing comma date-serialization map so it runs before the join, and it is a net-zero-line change to the (already 150-line, at the limit) function: Preserved behavior (nothing else changes): elements are still serialized via (a is an object, so it is explicitly excluded from the throw). The existing + tests still pass unchanged. / elements are primitives under (), so they are unaffected — they still join as empty values. Primitive elements (string / number / boolean / symbol / bigint) join exactly as before. The **** path is covered by the same guard (it only adjusts the key prefix, at , after this point). Tests : Converted the two pre-existing skipped comma cases into real assertions that now expect a ( and ). Added a focused block asserting the throw for the reported repro, for the path, and for a nested-array element, plus a sanity assertion that a primitive comma array is unaffected (). Verification Base commit: Before (on base): After (this change): Full suite + lint (equivalent to minus the network audit): Alternative considered (discussion point) The maintainer also floated doing "something useful instead" of throwing. The main alternative is to encode nested structure inside the comma value (some server schemes do accept -style nesting), but there is no single unambiguous representation of an object inside a array — which is exactly why these tests were left as TODOs. Throwing is the safe, explicit, non-lossy default and matches the maintainer's primary suggestion; a future opt-in could layer a richer encoding on top without changing this guard. Minor related note: also treats a array element as a non-primitive, so now throws as well (previously it produced ). There is no existing test for that case; if buffers should be exempted they could be excluded alongside .
What changed Flat query strings now bypass per-key / construction and calls. The parser first proves that every parsed key can preserve its existing top-level meaning, then copies the already-parsed values into the result. Nested keys, dotted notation, decoded-dot keys, object input, empty keys, prototype-sensitive keys, and values that still require array compaction retain the existing path. Why The normal parser path builds and merges a one-key object for every parameter, even when a query contains only independent top-level keys. That repeated object construction and merging dominates large flat query strings. Performance CodeVetter ran ten paired, interleaved processes per revision after one warmup, using the identical Node test and ten parses per input size: The endpoint scaling exponent moved from 1.081 to 1.030. The paired verifier returned , , , and , with no recorded limitations. These numbers were measured on Node 24.19.0 / Apple M5 Pro and are intended to show the relative implementation effect, not hardware-independent throughput. Correctness and safety Broader direct-copy variants were rejected during development because the upstream suite caught mixed array/scalar, object-input, and decoded-dot semantic changes. The retained preflight falls back before copying any value when a key requires structural parsing. all 1,045 upstream tests pass 100% statement, function, and line coverage ESLint and EditorConfig checks pass with the repository's existing warnings only README evaluation passes distribution build passes passes The proposed commit changes only ; the benchmark harness is not included.
Repository: ljharb/qs. Description: A querystring parser and serializer with nesting support Stars: 8943, Forks: 912. Primary language: JavaScript. Languages: JavaScript (100%). License: BSD-3-Clause. Topics: browsers, encoding, javascript, node, nodejs, parse, querystrings, stringify, url-parsing. Open PRs: 49, open issues: 51. Last activity: 1mo ago. Community health: 85%. Top contributors: ljharb, nlf, papandreou, dead-horse, Connormiha, mizozobu, geek, Jokero, tdzienniak, elidoran and others.