Skip to content

Instantly share code, notes, and snippets.

@shovon
Last active May 13, 2025 07:14
Show Gist options
  • Save shovon/52dcb6f831a01ea81ccb5920ac00f3c1 to your computer and use it in GitHub Desktop.
Save shovon/52dcb6f831a01ea81ccb5920ac00f3c1 to your computer and use it in GitHub Desktop.
Some zod schema utilities for creating a Zod schema for a recursive structure
import { z } from "zod";
export type BaseNode<T extends string = "children"> = {
[K in T]?: BaseNode<T>[];
};
/**
* Creates a schema for a node that forms a directed tree.
*
* Usage:
*
* const SimpleTreeNode = baseNode("children", m => m))
*
* type SimpleTreeNode = z.infer<typeof SimpleTreeNode>
*
* If you want the children to be either a node or something else (such as a string):
*
* const NodeOrString = baseNode("children", m => z.union([m, z.string()]));
*
* type NodeOrString = z.infer<typeof NodeOrString>
*
* If you want to add fields to each node:
*
* const NodeWithStyle = baseNode("children", m => m.and(z.object({ style: z.unknown() })))
*
* type NodeWithStyle = z.infer<typeof NodeWithStyle>
* @param childrenKey A string that represents the field name that will be a list of children.
* @param modifier Useful for adding fields to each child node
* @returns A zod schema
*/
export const baseNode = <R, V extends string = "children">(
childrenKey: V,
modifier: (k: z.ZodType<BaseNode<V>>) => z.ZodType<R>
) =>
modifier(
z.lazy(
(): z.ZodType<BaseNode<V>> =>
z.object({
[childrenKey]: z.array(baseNode(childrenKey, modifier)).optional(),
}) as unknown as z.ZodType<BaseNode<V>>
)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment