The qs library is a popular utility for serializing and parsing query strings. It allows you to convert regular JavaScript objects into query string format and vice versa, with support for complex nested structures. Getting started is straightforward:
QueryParser.parse('numbers[]=1') // {numbers: ['1']}
Stringifier.stringify({numbers: [1]}) // numbers%5B0%5D=1
Both methods in qs accept an optional second parameter for configuration. The most useful configuration options include:
ignoreQueryPrefix and addQueryPrefix
The ignoreQueryPrefix option automatically removes the question mark (?) from the begining of location.search before parsing. Setting addQueryPrefix to true adds a question mark (?) during serialization:
// Parsing
QueryParser.parse('?value=1') // {?value: "1"}
QueryParser.parse('?value=1', {ignoreQueryPrefix: true}) // {value: "1"}
// Serialization
Stringifier.stringify({value: "1"}) // value=1
Stringifier.stringify({value: "1"}, {addQueryPrefix: true}) // ?value=1
Array Parsing and Serialization
Array serialization can be performed in several formats: indices, brackets, repeat, and comma, which control how the resulting string is formatted. Consider these examples:
Stringifier.stringify({ items: ['apple', 'banana'] }, { arrayFormat: 'indices' })
// 'items[0]=apple&items[1]=banana'
Stringifier.stringify({ items: ['apple', 'banana'] }, { arrayFormat: 'brackets' })
// 'items[]=apple&items[]=banana'
Stringifier.stringify({ items: ['apple', 'banana'] }, { arrayFormat: 'repeat' })
// 'items=apple&items=banana'
Stringifier.stringify({ items: ['apple', 'banana'] }, { arrayFormat: 'comma' })
// 'items=apple,banana'
These four formats produce increasingly concise results, but when dealing with nested arrays, they cause varying degrees of information loss, with the loss becoming more severe as the format becomes more compact. Using these formats on { data: [['item'], 'value'] } demonstrates this:
Stringifier.stringify({ data: [['item'], 'value'] }, { arrayFormat: 'indices' })
QueryParser.parse(Stringifier.stringify({ data: [['item'], 'value'] }, { arrayFormat: 'indices' })) // { data: [['item'], 'value'] }
QueryParser.parse(Stringifier.stringify({ data: [['item'], 'value'] }, { arrayFormat: 'brackets' })) // {data: ["item", "value"]}
QueryParser.parse(Stringifier.stringify({ data: [['item'], 'value'] }, { arrayFormat: 'repeat' })) // {data: ["item", "value"]}
QueryParser.parse(Stringifier.stringify({ data: [['item'], 'value'] }, { arrayFormat: 'comma' })) // {data: "item,value"}
Therefore, when working with nested data, the indices format is recommended, and conveniently, this is the default mode.
Custom Delimiters
The delimiter option allows you to specify which character to use as a separator. This is particularly useful for parsing cookies, which use semicolons (;) as separators:
document.cookie // "_ga=GA1.2.806176131.1570244607; _jsuid=1335121594; _gid=GA1.2.1453554609.1575990858"
QueryParser.parse(document.cookie, {delimiter:'; '})
Handling Number Types
As shown in the first example, when you serialize a number and then parse it back, you don't get a number but a string:
QueryParser.parse(Stringifier.stringify({count:1})) // {count: '1'}
If you want the parsed result to remain a number, you can implement a custom decoder:
QueryParser.parse('count[0]=1', {
decoder(input, defaultEncoder, charset, type) {
if (/^(\d+|\d*\.\d+)$/.test(input)) {
return parseFloat(input)
}
return input
}
})
You can extand this to handle Chinese characters by adding:
if (/^%[A-Za-z0-9+/]/.test(input)) {
return decodeURIComponent(input)
}