🔍 Search Terms
cyclical, recursive, inference, self-referential, extends, schema, zod
I'm trying to get recursive schemas working in Zod, powered by TypeScript's ability to handle self-reference inside getters. Example:
const Category = z.object({
name: z.string(),
get parent(){
// ^ 'parent' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.ts(7023)
return Category.optional()
}
})
This would be a massive usability win for Zod and other schema/ORM libraries. None of these are able to directly represent cyclical types currently without redundant type annotations or some "scope"/"registry" concept to defer type resolution and later perform some kind of recursive substitution.
My understanding is that TypeScript is generally capable of inferring a recursive type with it's ... approach as long as there aren't any assignability constraints on the data being inferred.
const a = {
get self() {
return a;
},
}
Applying any constraint breaks this capability. But in the case below, it seems potentially possible to typecheck the cyclical type against the constraint without issue.
const a = {
get self() {
// ^ does not have a return type annotation and is referenced directly or indirectly in one of its return expressions
return a;
},
} satisfies { self: any };
I think if the compiler was able to properly infer a above despite the satisfies clause, Zod could then support recursive objects in the way I'd described.
✅ Viability Checklist
⭐ Suggestion
Support inference on self-referential/cyclical data structures in more cases
📃 Motivating Example
Below is a more motivated example that I think demonstrates the same fundamental limitation I referenced earlier.
// base type
interface ZodType {
optional: "true" | "false";
output: any;
}
// string
interface ZodString extends ZodType {
optional: "false";
output: string;
}
// object
type ZodShape = Record<string, any>;
type Prettify<T> = { [K in keyof T]: T[K] } & {};
type InferObjectType<Shape extends ZodShape> = Prettify<
{
[k in keyof Shape as Shape[k] extends { optional: "true" } ? k : never]?: Shape[k]["output"];
} & {
[k in keyof Shape as Shape[k] extends { optional: "true" } ? never : k]: Shape[k]["output"];
}
>;
interface ZodObject<T extends ZodShape> extends ZodType {
optional: "false";
output: InferObjectType<T>;
}
// optional
interface ZodOptional<T extends ZodType> extends ZodType {
optional: "true";
output: T["output"] | undefined;
}
// factories
declare function object<T extends ZodShape>(shape: T): ZodObject<T>;
declare function string(): ZodString;
declare function optional<T extends ZodType>(schema: T): ZodOptional<T>;
// recursive type inference error
const Category = object({
name: string(),
get parent() {
// ^ 'parent' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.ts(7023)
return optional(Category);
},
});
💻 Use Cases
- What do you want to use this for?
To support more ergonomic representations of inherently cyclical data structures (Zod schemas, database schemas, state diagrams, etc)
- What shortcomings exist with current approaches?
Not possible
- What workarounds are you using in the meantime?
None/not possible
🔍 Search Terms
cyclical, recursive, inference, self-referential, extends, schema, zod
I'm trying to get recursive schemas working in Zod, powered by TypeScript's ability to handle self-reference inside getters. Example:
This would be a massive usability win for Zod and other schema/ORM libraries. None of these are able to directly represent cyclical types currently without redundant type annotations or some "scope"/"registry" concept to defer type resolution and later perform some kind of recursive substitution.
My understanding is that TypeScript is generally capable of inferring a recursive type with it's
...approach as long as there aren't any assignability constraints on the data being inferred.Applying any constraint breaks this capability. But in the case below, it seems potentially possible to typecheck the cyclical type against the constraint without issue.
I think if the compiler was able to properly infer
aabove despite thesatisfiesclause, Zod could then support recursive objects in the way I'd described.✅ Viability Checklist
⭐ Suggestion
Support inference on self-referential/cyclical data structures in more cases
📃 Motivating Example
Below is a more motivated example that I think demonstrates the same fundamental limitation I referenced earlier.
💻 Use Cases
To support more ergonomic representations of inherently cyclical data structures (Zod schemas, database schemas, state diagrams, etc)
Not possible
None/not possible