zod: 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

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 7
  • Comments: 27

Most upvoted comments

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.

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 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

Please open up, this issue is still active.

@scotttrinh

This does not seem like a closed issue and thus should be re-opened.

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'                                                                                                               
      }                                                                                                                                     
    ]);                                                                                                                                     
  });                                                                                                                                       
});
`

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 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

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

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;

@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’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

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;

Do you have strict: true in your tsconfig.json? I cannot reproduce the issue in the TypeScript playground:

TypeScript Playground

Screen Shot 2022-06-06 at 11 17 24 AM

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”

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?

If strict:false in tsconfig.json then will this fail?