Demand for VSCode Extension?
See original GitHub issueWas playing around with VSCode and Zod based on #53 – seeing if there is enough demand for this to put time into the concept. It is slow in the preview as I built that out in about 1 hour, but seems it could be nice - and make it easier to use Zod since you wouldn’t really need to even learn all the specifics - you could just create your TypeScript Type then translate it.
It utilizes the TypeScript Compiler API to read the AST and infer from there.
Since this is just using the TypeScript compiler API - this could pretty easily become a browser tool as well which you could just paste in your type and get a valid zod schema out from it.
On that note, this is more of a Typescript Plugin than a VSCode Extension - since the vscode part of it is just providing the menu option to run the TS Compiler and it uses the TS Compiler to build Typescript Code.
export const generatePrimitive = ({ kind, name, props, zodImportValue }: {
kind: ts.SyntaxKind;
name: string;
zodImportValue: string;
props: { isOptional: boolean; errorMessage: undefined | string; isNullable?: boolean }
}) => {
let flags = '';
if (props.isOptional) {
flags += '.optional()';
}
if (props.isNullable) {
flags += '.nullable()';
}
let errorMessage = props.errorMessage ? wrapQuotes(props.errorMessage) : '';
switch (kind) {
case ts.SyntaxKind.NumericLiteral:
return `${zodImportValue}.literal(${name})${flags}`;
case ts.SyntaxKind.StringLiteral:
return `${zodImportValue}.literal(${wrapQuotes(name)})${flags}`;
case ts.SyntaxKind.StringKeyword:
return `${zodImportValue}.string()${flags}`;
case ts.SyntaxKind.BooleanKeyword:
return `${zodImportValue}.boolean()${flags}`;
case ts.SyntaxKind.NullKeyword:
return `${zodImportValue}.null()${flags}`;
case ts.SyntaxKind.UndefinedKeyword:
return `${zodImportValue}.undefined()${flags}`;
case ts.SyntaxKind.NumberKeyword:
return `${zodImportValue}.number()${flags}`;
case ts.SyntaxKind.AnyKeyword:
return `${zodImportValue}.any()${flags}`;
case ts.SyntaxKind.BigIntKeyword:
return `${zodImportValue}.bigint()${flags}`;
case ts.SyntaxKind.VoidKeyword:
return `${zodImportValue}.void()${flags}`;
case ts.SyntaxKind.ClassKeyword: {
if (name === 'Date') {
return `${zodImportValue}.date()${flags}`;
}
// TODO : Handle Class & InstanceOf based on symbol & import detection context
}
default:
return `${zodImportValue}.any(${errorMessage})`;
}
};
The code above just uses strings to build it which was easier in this case. For those potentially interested, to move to using the TypeScript Compiler / AST to build it all, the generated type in the gif would be something like:
[
ts.createVariableStatement(
undefined,
ts.createVariableDeclarationList(
[ts.createVariableDeclaration(
ts.createIdentifier("TestSchema"),
undefined,
ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier("z"),
ts.createIdentifier("object")
),
undefined,
[ts.createObjectLiteral(
[
ts.createPropertyAssignment(
ts.createIdentifier("three"),
ts.createCall(
ts.createPropertyAccess(
ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier("z"),
ts.createIdentifier("literal")
),
undefined,
[ts.createStringLiteral("hi")]
),
ts.createIdentifier("optional")
),
undefined,
[]
)
),
ts.createPropertyAssignment(
ts.createIdentifier("one"),
ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier("z"),
ts.createIdentifier("number")
),
undefined,
[]
)
),
ts.createPropertyAssignment(
ts.createIdentifier("two"),
ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier("z"),
ts.createIdentifier("literal")
),
undefined,
[ts.createNumericLiteral("3")]
)
),
ts.createPropertyAssignment(
ts.createIdentifier("four"),
ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier("z"),
ts.createIdentifier("string")
),
undefined,
[]
)
),
ts.createPropertyAssignment(
ts.createIdentifier("five"),
ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier("z"),
ts.createIdentifier("date")
),
undefined,
[]
)
)
],
true
)]
)
)],
ts.NodeFlags.Const
)
),
ts.createTypeAliasDeclaration(
undefined,
undefined,
ts.createIdentifier("Test"),
undefined,
ts.createTypeReferenceNode(
ts.createQualifiedName(
ts.createIdentifier("z"),
ts.createIdentifier("infer")
),
[ts.createTypeQueryNode(ts.createIdentifier("TestSchema"))]
)
)
];
–>
const TestSchema = z.object({
three: z.literal('hi').optional(),
one: z.number(),
two: z.literal(3),
four: z.string(),
five: z.date()
})
type Test = z.infer<typeof TestSchema>;
Issue Analytics
- State:
- Created 3 years ago
- Reactions:34
- Comments:11 (3 by maintainers)
Top GitHub Comments
https://github.com/bradennapier/zod-web-converter
Here it is running in web browser by just modifying the ts-ast-viewer source to include the conversion command. Could be a powerful tool to host where people could just open it and paste in their types and get the values they need out from that.
Could pretty easily also convert graphql as requested to be a pretty awesome all-encompassing tool.
You can build and play for yourself pretty easily just follow the start instructions. This is compiling a bunch of versions of typescript you can choose so it takes a bit to compile but that isn’t really needed.
😛
@bradennapier I think I have a nice base for this kind of vscode extension, I tried to cover everything with unit tests & avoid any typecasting/any to be able to throw an explicit error when typescript can’t be translated into a zod schema. I also did find a nice trick for the “confidence” problem, I do generate an integration tests that compare
z.infer
and the original types (https://github.com/fabien0102/ts-to-zod/blob/main/example/heros.integration.ts).I still need to think a bit how, but I guess we should be able to bake this integration test part inside the generation process 🤔
The project -> https://github.com/fabien0102/ts-to-zod
Since this issue seams to be quite popular, I will probably try to make a VSCode extension with this new library as core 😃 (I must admit that this is a really fun project 😁)
Thanks again for your little POC 💯