Skip to content

Commit a07ff78

Browse files
committed
Parse boolean
1 parent c257cf2 commit a07ff78

File tree

4 files changed

+38
-10
lines changed

4 files changed

+38
-10
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ However, some additional input cases are handled:
1919

2020
- For `z.number()`, `z.boolean()`, `z.date()`, `z.object()`, and `z.record()`,
2121
whitespace only values are parsed as `null`.
22-
- For `z.boolean()`:
23-
- `1`, `True`, and `TRUE` are all parsed as `true`
24-
- `0`, `False`, and `FALSE` are all parsed as `true`
22+
- For `z.number()`, `z.boolean()`, `z.date()`,
23+
starting and ending whitespace is trimmed before parsing.
24+
- For `z.boolean()`, the following values are parsed as `true`:
25+
-
26+
- For `z.boolean()`, the following values are parsed as `false`:
27+
-
2528
- Parses `z.array()` in the following formats.
2629
In order to support unambiguous parsing, array string values
2730
containing a `,` are not supported.

src/lib/parse.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ test('parseUrlSearchParams: with URLSearchParams input', (t) => {
2424
)
2525
})
2626

27+
test.todo('pass though number values that parse as NaN')
28+
test.todo(
29+
'pass though boolean values that do not parse as truthy or falsy values',
30+
)
31+
2732
// e.g., foo.bar= would conflict with foo.bar.a= or foo.bar.b=2
2833
// since this would be a null object containing values (null is still a value).
2934
test.todo('cannot parse conflicting object keys')

src/lib/parse.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,35 @@ const parseFromParamSchema = (
4949
}
5050

5151
const parse = (k: string, values: string[], type: ValueType): unknown => {
52-
// TODO: Add better errors with coercion. If coercion fails, passthough?
5352
if (values.length === 0) return undefined
5453

5554
if (values[0] == null) {
5655
throw new Error(`Unexpected nil value when parsing ${k}`)
5756
}
5857

59-
if (type === 'number') return parseNumber(values[0])
60-
if (type === 'boolean') return values[0] === 'true'
58+
if (type === 'number') return parseNumber(values[0].trim())
59+
if (type === 'boolean') return parseBoolean(values[0].trim())
6160
if (type === 'string') return String(values[0])
6261
if (type === 'string_array') return values
6362
if (type === 'number_array') return values.map((v) => Number(v))
6463
throw new UnparseableSearchParamError(k, 'unsupported type')
6564
}
6665

67-
const parseNumber = (v: string): number | null => {
68-
if (v.trim().length === 0) return null
69-
return Number(v)
66+
const parseNumber = (v: string): number | null | string => {
67+
if (v.length === 0) return null
68+
const n = Number(v)
69+
if (isNaN(n)) return v
70+
return n
71+
}
72+
73+
const truthyValues = ['true', 'True', 'TRUE', 'yes', 'Yes', 'YES', '1']
74+
const falsyValues = ['false', 'False', 'FALSE', 'no', 'No', 'NO', '0']
75+
76+
const parseBoolean = (v: string): boolean | null | string => {
77+
if (v.length === 0) return null
78+
if (truthyValues.includes(v)) return true
79+
if (falsyValues.includes(v)) return false
80+
return v
7081
}
7182

7283
export class UnparseableSearchParamError extends Error {

test/generous-parsing.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,28 @@ const parseEmptyOrWhitespace = test.macro({
1111
const schema = z.object({ foo: type })
1212
const expected = { foo: null }
1313
t.deepEqual(parseUrlSearchParams('foo=', schema), expected)
14+
t.deepEqual(parseUrlSearchParams('foo= ', schema), expected)
15+
t.deepEqual(parseUrlSearchParams('foo= ', schema), expected)
1416
t.deepEqual(parseUrlSearchParams('foo=%20', schema), expected)
15-
t.deepEqual(parseUrlSearchParams('foo= %20 ++ + ', schema), expected)
17+
t.deepEqual(parseUrlSearchParams('foo=%20%20%20', schema), expected)
1618
t.deepEqual(parseUrlSearchParams('foo=+', schema), expected)
19+
t.deepEqual(parseUrlSearchParams('foo=+++', schema), expected)
20+
t.deepEqual(parseUrlSearchParams('foo= %20 ++ +%20 ', schema), expected)
1721
},
1822
})
1923

2024
test('number', parseEmptyOrWhitespace, z.number())
25+
test('boolean', parseEmptyOrWhitespace, z.boolean())
2126

2227
test.todo('parse empty or whitespace boolean params as null')
2328
test.todo('parse empty or whitespace date params as null')
2429
test.todo('parse empty or whitespace object params as null')
2530
test.todo('parse empty or whitespace record params as null')
2631

32+
test.todo('trim whitespace before parsing number params')
33+
test.todo('trim whitespace before parsing boolean params')
34+
test.todo('trim whitespace before parsing date params')
35+
2736
test.todo('parse empty or whitespace array params as empty')
2837
test.todo(
2938
'cannot parse multiple empty or whitespace array params like foo=&foo=',

0 commit comments

Comments
 (0)