Skip to content

Commit

Permalink
feat: Add urlKeys & clearOnDefault support for serializer
Browse files Browse the repository at this point in the history
Closes #715.
  • Loading branch information
franky47 committed Oct 30, 2024
1 parent 4c2b46a commit 4f8e2f9
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 12 deletions.
49 changes: 49 additions & 0 deletions packages/nuqs/src/serializer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,53 @@ describe('serializer', () => {
'?int=0&str=&bool=false&arr=&json={%22foo%22:%22bar%22}'
)
})
test('support for global clearOnDefault option', () => {
const serialize = createSerializer(
{
int: parseAsInteger.withDefault(0),
str: parseAsString.withDefault(''),
bool: parseAsBoolean.withDefault(false),
arr: parseAsArrayOf(parseAsString).withDefault([]),
json: parseAsJson(x => x).withDefault({ foo: 'bar' })
},
{ clearOnDefault: false }
)
const result = serialize({
int: 0,
str: '',
bool: false,
arr: [],
json: { foo: 'bar' }
})
expect(result).toBe(
'?int=0&str=&bool=false&arr=&json={%22foo%22:%22bar%22}'
)
})
test('parser clearOnDefault takes precedence over global clearOnDefault', () => {
const serialize = createSerializer(
{
int: parseAsInteger
.withDefault(0)
.withOptions({ clearOnDefault: true }),
str: parseAsString.withDefault('')
},
{ clearOnDefault: false }
)
const result = serialize({
int: 0,
str: ''
})
expect(result).toBe('?str=')
})
test('supports urlKeys', () => {
const serialize = createSerializer(parsers, {
urlKeys: {
bool: 'b',
int: 'i',
str: 's'
}
})
const result = serialize({ str: 'foo', int: 1, bool: true })
expect(result).toBe('?s=foo&i=1&b=true')
})
})
35 changes: 23 additions & 12 deletions packages/nuqs/src/serializer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Options } from './defs'
import type { inferParserType, ParserBuilder } from './parsers'
import { renderQueryString } from './url-encoding'

Expand All @@ -6,7 +7,15 @@ type ParserWithOptionalDefault<T> = ParserBuilder<T> & { defaultValue?: T }

export function createSerializer<
Parsers extends Record<string, ParserWithOptionalDefault<any>>
>(parsers: Parsers) {
>(
parsers: Parsers,
{
clearOnDefault = true,
urlKeys = {}
}: Pick<Options, 'clearOnDefault'> & {
urlKeys?: Partial<Record<keyof Parsers, string>>
} = {}
) {
type Values = Partial<inferParserType<Parsers>>

/**
Expand All @@ -23,36 +32,38 @@ export function createSerializer<
*/
function serialize(base: Base, values: Values | null): string
function serialize(
baseOrValues: Base | Values | null,
values: Values | null = {}
arg1BaseOrValues: Base | Values | null,
arg2values: Values | null = {}
) {
const [base, search] = isBase(baseOrValues)
? splitBase(baseOrValues)
const [base, search] = isBase(arg1BaseOrValues)
? splitBase(arg1BaseOrValues)
: ['', new URLSearchParams()]
const vals = isBase(baseOrValues) ? values : baseOrValues
if (vals === null) {
const values = isBase(arg1BaseOrValues) ? arg2values : arg1BaseOrValues
if (values === null) {
for (const key in parsers) {
search.delete(key)
const urlKey = urlKeys[key] ?? key
search.delete(urlKey)
}
return base + renderQueryString(search)
}
for (const key in parsers) {
const parser = parsers[key]
const value = vals[key]
const value = values[key]
if (!parser || value === undefined) {
continue
}
const urlKey = urlKeys[key] ?? key
const isMatchingDefault =
parser.defaultValue !== undefined &&
(parser.eq ?? ((a, b) => a === b))(value, parser.defaultValue)

if (
value === null ||
((parser.clearOnDefault ?? true) && isMatchingDefault)
((parser.clearOnDefault ?? clearOnDefault ?? true) && isMatchingDefault)
) {
search.delete(key)
search.delete(urlKey)
} else {
search.set(key, parser.serialize(value))
search.set(urlKey, parser.serialize(value))
}
}
return base + renderQueryString(search)
Expand Down

0 comments on commit 4f8e2f9

Please sign in to comment.