zod
zod copied to clipboard
Add z.string().json(...) helper
Based on https://github.com/colinhacks/zod/issues/3077#issuecomment-1878470503. I started playing around to see how quick it would be to implement, and it ended up being very quick - so opening a PR in hopes the idea is accepted.
Copying from the readme for the z.string().json(...) method - the linked issue has more details on the motivation:
JSON
The z.string().json(...) method parses strings as JSON, then pipes the result to another specified schema.
const Env = z.object({
API_CONFIG: z.string().json(
z.object({
host: z.string(),
port: z.number().min(1000).max(2000),
})
),
SOME_OTHER_VALUE: z.string(),
});
const env = Env.parse({
API_CONFIG: '{ "host": "example.com", "port": 1234 }',
SOME_OTHER_VALUE: "abc123",
});
env.API_CONFIG.host; // returns parsed value
If invalid JSON is encountered, the syntax error will be wrapped and put into a parse error:
const env = Env.safeParse({
API_CONFIG: "not valid json!",
SOME_OTHER_VALUE: "abc123",
});
if (!env.success) {
console.log(env.error); // ... Unexpected token n in JSON at position 0 ...
}
This is recommended over using z.string().transform(s => JSON.parse(s)), since that will not catch parse errors, even when using .safeParse.
Deploy Preview for guileless-rolypoly-866f8a ready!
Built without sensitive environment variables
| Name | Link |
|---|---|
| Latest commit | 3a0cdb4cb8a100bb0d8a2b4442b82bbf118e2f8d |
| Latest deploy log | https://app.netlify.com/sites/guileless-rolypoly-866f8a/deploys/65a1b034f44f690008a4716c |
| Deploy Preview | https://deploy-preview-3109--guileless-rolypoly-866f8a.netlify.app |
| Preview on mobile | Toggle QR Code...Use your smartphone camera to open QR code link. |
To edit notification comments on pull requests, go to your Netlify site configuration.
@colinhacks any thoughts on this?
Thanks, great stuff. I'm merging this into the v4 branch as a starting point, but the API is likely to change before this lands. I actually think this would be better as a top-level z.json(), and z.string().json() should be reserved for verifying that the string is in fact a valid JSON string.
@colinhacks what would be the feature of a z.json?
Parsing and stringify?
Yes, a schema that encapsulates the JSON.parse step. Input is string, Output is inferred from the arguments:
const schema = z.json(z.object({ name: z.string() }))
schema.parse(`{ "name": "Maarten" }`)
That looks clean, however would that not be this: (?)
const jsonStr = `{ "name": "Jhon Doe" }`;
const schema = z.object({
name: z.string(),
});
const anyJsonObj: unknown = JSON.parse(jsonStr);
const parsed = schema.parse(anyJsonObj);
// ^? type: z.infer<typeof schema>
I could be wrong, but the .json would be a replacement for JSON.parse, where this function: MDN Docs: JSON.parse also takes in a reviver, how are you planning to resolve that since any object can be different, users can make their own revivers and set custom properties to, for example, make a map or set in JSON.
So by making this 'replacement' as to call it, would you not need way more steps to have the same, generic, result?
Was just going to comment re: this, it would be good to have a method like
const Config = z.json(
z.object({
apiKey: z.string(),
templateId: z.string(),
})
).reviver((k, v) => typeof v === 'number' ? v.toString() : v)
const result = Config.parse(process.env.CONFIG) // '{"apiKey":"x", "templateId":123}' => {apiKey: 'x', templateId: '123'}
You could often achieve a similar result with .transform or .preprocess but there are still plenty of valid use cases for reviver.
But @m10rten it's not really the same as calling JSON.parse(jsonStr) - that will throw an error directly. It won't be encapsulated by safeParse, won't be wrapped as a zod error. And it becomes much more inconvenient to compose, e.g. if it's in a nested property like:
const BigSchema = z.object({
type: z.string(),
foo: z.number(),
env: z.object({
NODE_ENV: z.string(),
CONFIG: z.json(z.object({apiKey: z.string()})).reviver(...)
}),
})