Closed FlorianWendelborn closed 3 years ago
Hello! Care to elaborate with an example?
@StefanTerdell sure, here’s the documentation section for z.input
/z.output
: https://github.com/colinhacks/zod#what-about-transforms
While the example is an extreme case where it switches from string
to number
, a more common one would be using .default
which switches from T | undefined
to just T
.
So, inferring z.input
would yield T | undefined
while the output (post-parsing) is just T
.
const usingDefault = z.string().default('example')
type zInput = z.input<typeof usingDefault> // should be string | undefined
type zOutput = z.output<typeof usingDefault> // should be string (z.infer is an alias for z.output)
Thanks!
I may have been eating crayons again, but I don't see how this is relevant when converting to a Json Schema as the output schema would depend on the implementation of the Json Schema parser.
For instance, Ajv will quietly ignore defaults unless the useDefaults flag is passed as true and will leave the property undefined if not. Like so:
const mySchema = z.object({
myString: z.string().default("hello"),
});
const jsonSchema = zodToJsonSchema(mySchema);
const ajvWithDefaults = new Ajv({ useDefaults: true });
const dataWithDefaults = {}
ajvWithDefaults.validate(jsonSchema, dataWithDefaults)
console.log(dataWithDefaults)
// outputs: { myString: "hello" }
const ajvWithoutDefaults = new Ajv();
const dataWithoutDefaults = {}
ajvWithoutDefaults.validate(jsonSchema, dataWithoutDefaults)
console.log(dataWithoutDefaults)
// outputs: { }
Zod marks fields within an object with defaults as optional both in input and output and only "secretly guarantees" a value in the end. Like so:
const mySchema = z.object({
myString: z.string().default("hello"),
});
type input = z.input<typeof mySchema>;
// Compiles to:
// type input = {
// myString?: string;
// }
type output = z.output<typeof mySchema>;
// Also compiles to:
// type output = {
// myString?: string;
// }
const data = mySchema.parse({})
// data = {
// myString: "hello"
// }
The example you're using is also a bit broken since both types are inferred to simply string
. In zod^3.x.x, optional fields aren't unions with undefined anymore under the hood.
But I'm getting the feeling that I'm missing something here.. Would be open to a clearer issue or a PR of course, but I think I'll go ahead and close this issue in the meantime.
Thanks for reaching out!
Regarding your example, I think this may be different in the way you tested it. Perhaps a different TS version or no strict mode?
Here’s a repro for yielding two different input/output types from your example: TypeScript Playground
As you can see, the tooltip shows it as non-optional, as it should:
Here is the repro of my example, where you can also see it’s string | undefined
vs string
respectively:
I don't see how this is relevant when converting to a Json Schema
As far as I’m concerned, at least for my particular use-cases so far, JSON Schemas are just an intermediary step to convert X -> JSON -> TypeScript Interfaces with X being OpenAPI or zod
.
And I’d be surprised if the JSON schema for an optional property looks exactly the same as the JSON schema for a non-optional property. I don’t really want to use JSON schemas in the first place, but they’re a pretty widely-supported intermediary between different typed formats, so maybe my assumptions are somehow wrong. That’s also why I can’t provide the expected generated types, as I’ve never used JSON schemas for anything but converting it to something else.
Also, there’s still the other edge-case with transforms where the input type could be string while the output is number. That certainly has to be reflected in the schema (although I personally only care about the default/partial/undefined support as I don’t use other forms of transforms)
Would you look at that! Looks like I have some form of environmental issue.
Anyway, I took a look at it and it seems like z.output just uses the inferred return type from the transform function, so there's no actual runtime schema for the output. In other words: it can't be done with the current Zod implementation. Sorry :(
Maybe running https://www.npmjs.com/package/ts-json-schema-generator or something like it on the inferred type could work but I haven't given it a try.
What I would need is something like transformInto(nextSchema, func) that would pass the new schema down the chain. There's a PR here for something similiar, but looks like it's gone stale: https://github.com/colinhacks/zod/pull/420
Then I think this should just be documented that this package handles z.input and not z.output aka z.infer
Currently, this package seems to only be able to generate
z.input
. There’s differences between the two when using.default()
for example.