key paths for recursive nested types

Hey all, I have the following type
type Schema = {
name: string,
description: string,
type: "string" | "number" | "boolean" | "array" | "object",
enum?: string[],
properties?: Schema[],
items?: Omit<Schema, "name" | "description">,
}

const schema: z.ZodType<Schema> = z.object({
name: z.string(),
description: z.string(),
type: z.union([z.literal("string"), z.literal("number"), z.literal("boolean"), z.literal("array"), z.literal("object")]),
enum: z.array(z.string()).optional(),
properties: z.array(z.lazy(() => schema)).optional(),
items: z.lazy(() => schema).optional(),
})
type Schema = {
name: string,
description: string,
type: "string" | "number" | "boolean" | "array" | "object",
enum?: string[],
properties?: Schema[],
items?: Omit<Schema, "name" | "description">,
}

const schema: z.ZodType<Schema> = z.object({
name: z.string(),
description: z.string(),
type: z.union([z.literal("string"), z.literal("number"), z.literal("boolean"), z.literal("array"), z.literal("object")]),
enum: z.array(z.string()).optional(),
properties: z.array(z.lazy(() => schema)).optional(),
items: z.lazy(() => schema).optional(),
})
and am trying to build out a type that represents all the possible key paths that could be created through the nesting of properties or items. I've tried a few patterns and have had chatGPT spit types out, but none of them seem to work. The closest thing I could get to was this
type Keypath<T> = T extends { properties: Schema[] }
? { [K in keyof T['properties']]: `properties.${K}` | Keypath<T['properties'][K]> }[keyof T['properties']]
: T extends { items: Omit<Schema, "name" | "description"> }
? `items.${number}` | Keypath<T['items']>
: never;
type Keypath<T> = T extends { properties: Schema[] }
? { [K in keyof T['properties']]: `properties.${K}` | Keypath<T['properties'][K]> }[keyof T['properties']]
: T extends { items: Omit<Schema, "name" | "description"> }
? `items.${number}` | Keypath<T['items']>
: never;
For now, I've settled to using regex through zod to represent the type
const formSchemaKeyPath = z.string().regex(/^(functions)((\.[0-9]+)(\.(properties|items)))*/)
const formSchemaKeyPath = z.string().regex(/^(functions)((\.[0-9]+)(\.(properties|items)))*/)
If anyone knows the solution to this, please do let me know
7 Replies
erik.gh
erik.gh12mo ago
can you give an example type of what the result should be?
Sz
Sz12mo ago
functions | functions.${number}.items | functions.${number}.properties
erik.gh
erik.gh12mo ago
okay and what are you missing with this type?
Sz
Sz12mo ago
It should be following this pattern of regex ^(functions)((\.[0-9]+)(\.(properties|items)))*. The missing part here is that the ${number}.items and ${number}.properties can be repeated any number of times in any combination of the 2
erik.gh
erik.gh12mo ago
okay so as long as the number of repetitions is not fixed i don't think this is possible because you cannot have a circular reference in a type alias: https://www.typescriptlang.org/play?#code/C4TwDgpgBAgswCcCWAjArsCBnKBeKABgHQAkA3gHZoC2KECAvqWQORKbVYtQA+ULYBAHtICYEmwsG5Ftz5xEqDNgYEAUKEhQA0hHABDYAAs8hAGZoKAY3FCKWGXNjxk6TFlVqrdrMCj6ALh09MEMTfBYLa1t7IgBGIkERenFJIA
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
erik.gh
erik.gh12mo ago
is there maybe a fixed object that you are trying to derive the path from?
Sz
Sz11mo ago
Not necessarily, it's supposed to represent a generic object type that can have both nested objects and arrays
Want results from more Discord servers?
Add your server