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 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.
So if i understand correctly, the addition of keyof made superset assignment to a subset impossible.
Since i made the implementation of keyof, i can try to create a separate helper like suggested. z.keyOf() and remove the method from ZodObject.
@colinhacks what do you think ? this will be a breaking change, but i guess the keyof feature is not that much used ATM since it's new.
if i understand correctly, the addition of keyof made superset assignment to a subset impossible.
Exactly! Thanks for considering this.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Not stale, still an issue that prevents us from updating zod
I suppose comments don't count as activity? This issue continues to prevent us from updating zod, so I wouldn't consider it stale.