zod
zod copied to clipboard
safeParse discriminated union doesn't have 'error' attribute
const parsedUser = userZ.safeParse(user.toObject())
if (!parsedUser.success) {
console.error(parsedUser.error)
return null
}
Gives me this error:
Property 'error' does not exist on type 'SafeParseReturnType<{ telegramId?: number; telegramHandle?: string; twitterId?: number; twitterHandle?: string; address?: string; ens?: string; telegramFirstName?: string; twitterUsername?: string; isMetabotEnabled?: boolean; }, { ...; }>'.
Property 'error' does not exist on type 'SafeParseSuccess<{ telegramId?: number; telegramHandle?: string; twitterId?: number; twitterHandle?: string; address?: string; ens?: string; telegramFirstName?: string; twitterUsername?: string; isMetabotEnabled?: boolean; }>'.ts(2339)
when success===true
it doesn't give an error when i try to access data
. Example:
return parsedUser.success ? parsedUser.data : null
Do you have strict: true
in your tsconfig.json
? I cannot reproduce the issue in the TypeScript playground:
If strict:false
in tsconfig.json
then will this fail?
I am having the same errors trying to access address.data
on the last line
const address = addressSchema.safeParse(req.body);
if (!address.success) {
res.status(400).end({ error: address.error });
resolve();
}
const parcedAddress: addressPayload = address.data;
Property 'data' does not exist on type 'SafeParseError
Property 'data' does not exist on type 'SafeParseReturnType
I found a solution:
This fails
const result = validator.safeParse(example);
if (!result.success) {
result.error // this shows a TS error
}
This works
const result = validator.safeParse(example);
if (result.success === false) {
result.error // it works!
}
Perhaps the docs should update the example so users can infer the correct types.
Hi, @Daniel-Griffiths!
I am using zod v3.14.5
and have no issues using the following:
const AddressSchema = z
.object({
id: z.string(),
street: z.string(),
number: z.string()
})
// parse & validate payload
const address = AddressScheme.safeParse(req.body);
if (!address.success) return res.status(400).send(address.error.issues);
const parsedAddress = address.data;
I think the return values of the safeParse and its types needs to be make more consistent. I think both cases must have the same fields, just with different values, either them being the value or null.
type safeParseReturn<T> = { success: true, error: null, data: T } | { sucess: false, error: Error, data: null }
I don't know if there is any specific ts reason to not do it like this
I'm also having a very similar issue to this & causing my build to fail.
ERROR: src/pages/contact.tsx:24:15 - error TS2339: Property 'data' does not exist on type 'SafeParseReturnType<{ message?: string; name?: string; email?: string; }, { message?: string; name?: string; email?: string; }>'.
CODE:
const { data, error } = ContactForm.safeParse({ name, email, message });
I took the code from the Zod documentation as well but it seems that the SafeParseReturnType doesn't contain either a 'data' or 'error' key that I can look into
@ytsruh
SafeParseReturnType
is a discriminated union, so you have to narrow the type before accessing the properties of the result object. A few of the examples above show how it's done, but here's your same example:
const result = ContactForm.safeParse({ name, email, message });
if (!result.success) {
const { error } = result;
// use `error`
}
const { data } = result;
// use `data`
I think the return values of the safeParse and its types needs to be make more consistent. I think both cases must have the same fields, just with different values, either them being the value or null.
type safeParseReturn<T> = { success: true, error: null, data: T } | { sucess: false, error: Error, data: null }
I don't know if there is any specific ts reason to not do it like this
One could simply use the error and data nullability and not need the success
key at all since error
and data
would serve as their own discriminants. Definitely a design choice, but I think it's consistent with common TypeScript usage of discriminated unions where the keyset of the object is determined by the discriminant, not just the types of the values.
Since no one has demonstrated any bug or incorrect results here, I'm going to close this issue so that it doesn't become a dumping ground for similar issues or related discussions.
Here is a demo of the issue.
Note foo()
fails when narrowing using if (!result.success) {
to narrow while bar()
passes using if (result.success === false) {
https://stackblitz.com/edit/angular-ivy-hytjux?file=src/app/app.component.ts
Found the issue.
In order to do this, if (!result.success)
, strictNullChecks must be set to true.
Alternatively, you can do this:
if (result.success === false)
or
if (!result.success) {
const { error } = result as SafeParseError;
Found the issue.
In order to do this,
if (!result.success)
, strictNullChecks must be set to true.Alternatively, you can do this:
if (result.success === false)
or
if (!result.success) { const { error } = result as SafeParseError;
I have strict: true, but I'm still seeing errors when doing !result.success
. I'm still having to do a manual comparison with ===
.
I am having the same errors trying to access
address.data
on the last lineconst address = addressSchema.safeParse(req.body); if (!address.success) { res.status(400).end({ error: address.error }); resolve(); } const parcedAddress: addressPayload = address.data;
Property 'data' does not exist on type 'SafeParseError
Property 'data' does not exist on type 'SafeParseReturnType
I get this error I wonder why, it works on other files though
You need to return the resolve call. Like return resolve()
On Wed, 11 Jan 2023 at 18:51, Lluie Cain Andaya @.***> wrote:
I am having the same errors trying to access address.data on the last line
const address = addressSchema.safeParse(req.body); if (!address.success) { res.status(400).end({ error: address.error }); resolve(); } const parcedAddress: addressPayload = address.data;
Property 'data' does not exist on type 'SafeParseError Property 'data' does not exist on type 'SafeParseReturnType I get this error I wonder why, it works on other files though
— Reply to this email directly, view it on GitHub https://github.com/colinhacks/zod/issues/1190#issuecomment-1379266600, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARKJWOE6HG46ETT2JABQHLWR3XLHANCNFSM5X5GLDNQ . You are receiving this because you commented.Message ID: @.***>
--
https://danielorodriguez.com
I think the return values of the safeParse and its types needs to be make more consistent. I think both cases must have the same fields, just with different values, either them being the value or null.
type safeParseReturn<T> = { success: true, error: null, data: T } | { sucess: false, error: Error, data: null }
I don't know if there is any specific ts reason to not do it like this
I pretty much think this is a good idea, library apis that always return same shape results are much easier to work with and reason about.
So I have made a wrapper for safeParse
providing a predictable API to work with:
import { z, Schema } from "zod";
interface ParseResult {
data: object | null;
error: object | null;
}
function safeParse(schema: Schema, inputData: object):ParseResult {
var data = null, error = null;
try {
const result = schema.parse(inputData);
data = result;
} catch (e) {
error = e
}
return { data, error }
}
// Now you can easily have flows like
const ProjectSchema = z.object({
title: z
.string(),
description: z.string(),
});
const testData = {title: 'foo' , description: 'bar'};
const {data, error} = safeParse( ProjectSchema, testData);
Maybe some on find it useful too.
Since no one has demonstrated any bug or incorrect results here, I'm going to close this issue so that it doesn't become a dumping ground for similar issues or related discussions.
How is https://stackblitz.com/edit/typescript-baclo6?file=index.ts not a problem? To be fair I don't know whether it's typescript or zod issue, it exists, doesn't it?
I've noticed that this issue as well:
const foo = z.string().safeParse('hi')
if (!foo.success) throw foo.error // Property 'error' does not exist on type 'SafeParseSuccess<string>'.
console.log(foo.data)
The resp.success === false
workaround works but feels awkward/buggy behavior as I've been able to use !resp.success
previously (not sure if this is a newly introduced issue?)
Using Zod 3.21.4
and TypeScript 4.9.5
(both latest as of writing)
I think the return values of the safeParse and its types needs to be make more consistent. I think both cases must have the same fields, just with different values, either them being the value or null.
type safeParseReturn<T> = { success: true, error: null, data: T } | { sucess: false, error: Error, data: null }
I don't know if there is any specific ts reason to not do it like this
I pretty much think this is a good idea, library apis that always return same shape results are much easier to work with and reason about.
So I have made a wrapper for
safeParse
providing a predictable API to work with:import { z, Schema } from "zod"; interface ParseResult { data: object | null; error: object | null; } function safeParse(schema: Schema, inputData: object):ParseResult { var data = null, error = null; try { const result = schema.parse(inputData); data = result; } catch (e) { error = e } return { data, error } } // Now you can easily have flows like const ProjectSchema = z.object({ title: z .string(), description: z.string(), }); const testData = {title: 'foo' , description: 'bar'}; const {data, error} = safeParse( ProjectSchema, testData);
Maybe some on find it useful too.
I think safeParse should have the same results as your implementation. I made some changes in your function to keep the type inference.
import z, { Schema } from 'zod';
interface ParseResult<Shape> {
data?: Shape;
error?: z.ZodError<Shape>;
}
const safeParse = <Shape>(
schema: Schema<Shape>,
inputData: Partial<Shape>
): ParseResult<Shape> => {
const result: ParseResult<Shape> = {};
const parsedData = schema.safeParse(inputData);
if (parsedData.success === false) {
result.error = parsedData.error;
} else {
result.data = parsedData.data;
}
return result;
};
describe('Test', () => {
it('should validate is required', () => {
const schema = z.object({ total: z.number() });
const result = safeParse(schema, {});
expect(result.data).toBeUndefined();
expect(result.error?.errors).toEqual([
{
code: 'invalid_type',
expected: 'number',
message: 'Required',
path: ['total'],
received: 'undefined'
}
]);
});
});
`
What solved for me was to set "strict": true
and "strictNullChecks": true
in the tsconfig.json
under the compilerOptions
.
The code below now works as expected (no TS complaints):
const result = schema.safeParse(value)
if (!result.success) {
log({ msg: 'Invalid input', error: result.error })
}
Currently using:
- "zod": "3.22.2"
- "typescript": "5.2.2"
strict
and strictNullChecks
doesn't really solve the issue that we can't do early returns by just checking if (result.error)
. it's almost purely a stylistic difference, but it's definitely unexpected and not at all "convenient" like the docs claim
if anything it should get a more honest call out and say that because it's a discriminated union, the usual early return syntax is reversed from what you normally see: you must check for a false
success, and not the existence of an error
@scotttrinh
This does not seem like a closed issue and thus should be re-opened.
This is still an issue
===
workaround works but it is easy to forget about. My editor is perfectly fine with if (result.success)
only to then fail on tsc
run
Please open up, this issue is still active.
solution `const validatedFields = RegisterSchema.safeParse(values);
if (!validatedFields.success) {
return { error: "Invalid fields!" };
}
const { email, password, name } = validatedFields.data;`
Easy, go to tsConfig.json and add "strict": true in compilerOptions.
I'm going to lock this issue: safeParse
uses a result-like discriminated union, and that's a design choice we chose explicitly. We are not likely to ever reverse course on the design of this API.
I'll take a quick stab at a few of the recent messages here:
=== workaround works but it is easy to forget about. My editor is perfectly fine with if (result.success) only to then fail on tsc run
If you're having this issue: you do not have strict: true
or are turning it or strictNullChecks
off at some point in your config. We require this setting, and it's spelled out explicitly in the docs. Furthermore if your editor and tsc are disagreeing that sounds like an issue with your editor's setup (using a different tsconfig.ts
than tsc
maybe?)
doesn't really solve the issue that we can't do early returns by just checking if (result.error). it's almost purely a stylistic difference, but it's definitely unexpected and not at all "convenient" like the docs claim
That style of checking is common in JS and Go, but is (arguably) less idomatic TypeScript given result-like discriminated unions. If you disagree with this design on stylistic grounds, that's fine, you're free to disagree, but we have specifically chosen this API, so "I don't like it" isn't an argument we're going to find very compelling.
If I can make an argument for why we chose this it would be: In a language like TypeScript with discriminated unions, it seems less clear that a member of that union would have keys from other members of the union but just have them set to a constant null
like value. For instance (excuse the contrived example):
type Shape =
| { type: "Circle"; radius: number }
| { type: "Rectangle"; height: number; width: number }
vs
type Shape =
| { type: "Circle"; width: null; height: null; radius: number; }
| { type: "Rectangle"; width: number; height: number; radius: null; }
There is another more subtle issue here, too:
const schema = z.null();
const result = schema.safeParse(null);
if (result.data) {
// oops! this doesn't work because it's supposed to be `null`!
}
You might argue that users aren't likely to fall into this due to the if (result.error)
being used for early return, but this requires discipline rather than leveraging the type system to avoid weird foot guns like this.
At any rate, this is working as expected. If we want to maybe open a discussion about future changes to the API, feel free, but this isn't an issue or a bug. Thanks for everyone engaging in the discussion and bringing their ideas and use cases forward here, I don't want to come across as harsh or dismissive.