question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Addition of `.keyof()` breaks assignability for `ZodObjects`

See original GitHub issue

Hi there,

#1216 appears to break assignability of ZodObjects in a subtle way. For example, the following worked before Zod 3.17.9, but fails in the latest Zod (3.17.10). I’m on TypeScript version 4.7.4.

declare const superset: { foo: string } & z.ZodObject<{
  prop1: z.ZodString;
  prop2: z.ZodNumber;
}>; // The `{ foo: string }` intersection is necessary for this to fail for some reason.

const subset: z.ZodObject<{ prop1: z.ZodString }> = superset;
//    ^^^^^^ Error here

This produces the following type error:

Type '{ foo: string; } & ZodObject<{ prop1: ZodString; prop2: ZodNumber; }, "strip", ZodTypeAny, { prop1: string; prop2: number; }, { prop1: string; prop2: number; }>' is not assignable to type 'ZodObject<{ prop1: ZodString; }, "strip", ZodTypeAny, { prop1: string; }, { prop1: string; }>'.
  The types returned by 'keyof()._parse(...)' are incompatible between these types.
    Type 'ParseReturnType<"prop1" | "prop2">' is not assignable to type 'ParseReturnType<"prop1">'.
      Type 'OK<"prop1" | "prop2">' is not assignable to type 'ParseReturnType<"prop1">'.
        Type 'OK<"prop1" | "prop2">' is not assignable to type 'OK<"prop1">'.
          Type '"prop1" | "prop2"' is not assignable to type '"prop1"'.
            Type '"prop2"' is not assignable to type '"prop1"'.ts(2322)

The problem arises because z.ZodEnum<["prop1", "prop2"]> is not assignable to z.ZodEnum<["prop1"]>. This makes sense! However, I would argue that superset should be assignable to subset, because that aligns with TypeScript’s structural typing. In particular, in TypeScript,

interface Superset {
  prop1: string;
  prop2: number;
}

is assignable to

interface Subset {
  prop1: string
}

As I mentioned in the comment, for some reason this error doesn’t surface unless you intersect the z.ZodObject with a non-empty object type (i.e., { foo: string } in the example above). As far as I can tell, this is a TypeScript quirk. I think with the addition of .keyof(), TypeScript should be raising an error even without that intersection present. Maybe I’m missing something?

Not sure what the solution is here other than removing .keyof() I guess. It could be replaced with z.keyOf(object: z.ZodObject) or something else that doesn’t change the ZodObject type? Or you could decide that this problem doesn’t matter! But a project I’m working on does lots of fun stuff with Zod and we rely on ZodObject assignability working this way 🥲.

Thanks for reading and for the fantastic library.

P.S. I feel like somehow the new variance annotations could be used to solve this but I’m not sure how.

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:2
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
mayorandrewcommented, Oct 11, 2022

Not stale, still an issue that prevents us from updating zod

0reactions
itsgiacoliketacocommented, Oct 18, 2022

I suppose comments don’t count as activity? This issue continues to prevent us from updating zod, so I wouldn’t consider it stale.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Addition of `.keyof()` breaks assignability for `ZodObjects`
Hi there,. #1216 appears to break assignability of ZodObjects in a subtle way. For example, the following worked before Zod 3.17.9, but fails...
Read more >
Typecheck schemas against existing types · Issue #372 - GitHub
TL;DR I would love to have a way to ensure that a Zod schema matches an existing type definition using the normal Typescript...
Read more >
How to use the keyof operator in TypeScript - LogRocket Blog
Let's break it down: keyof T returns a union of string literal types. The extends keyword is used to apply constraints to ...
Read more >
keyof Model not assignable to union of keys issue in generic ...
type ColumnDef<M extends AnyObject, F extends keyof M = keyof M> = F extends keyof M ... And your buildColumns() call is no...
Read more >
Designing the perfect TypeScript schema validation library
I recently built a fairly large amount of functionality around io-ts, adding support for mixed required/optional fields, eliminating the ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found