-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Data representation of schema? #507
Comments
Perhaps you're looking for import assert from "assert/strict";
import { z } from "zod";
const a = z.object({
foo: z.string(),
})
assert.ok(a.shape.foo instanceof z.ZodString) |
@scotttrinh perhaps, I'll check that out thanks! IIUC |
@scotttrinh there is a use-case I'm missing. I need a way to attach metadata to zod schema fields. I would like to attach something like this: .metadata({
graphqlMapping: 'Foo'
}) Where This is because I have some fields that I want to map to/re-use existing GraphQL types in the schema. |
@jasonkuhrt I don't think you're going to find a way to "attach metadata" to a You could invert this a bit if you want to avoid duplication, something like this: TypeScript Playground import assert from "assert/strict";
import { z } from "zod";
const personDef = {
name: {
schema: z.string(),
graphqlMapping: "Name",
},
};
const person = z.object({
name: personDef.name.schema,
})
assert.ok(a.shape.name instanceof z.ZodString) You could probably even write a helper that would define a schema based on one of these "def" objects, but I'll leave that as an exercise for the reader 😉 |
I think metadata would be nice to have. Might make a new feature request for that. import assert from "assert/strict";
import { z } from "zod";
const person = z.object({
name: z.string().metadata({ graphqlMapping: 'Name' }),
})
assert.ok(person.shape.name instanceof z.ZodString)
assert.ok(person.shape.name.metadata.graphqlMapping === 'Name') |
Hey Jason, cool to see you using Zod. I was an early fan/user of Nexus back in early 2019, great stuff. I'm opposed to the concept of adding metadata to schemas for reasons explained in #76 and #138. I think it's an antipattern since most people (not you though) want to use it for contextual refinements that break encapsulation. Also TypeScript won't be aware of the type signature of the metadata in most scenarios, e.g. if you're looping over an array of ZodObjects to generate a GraphQL schema (which it seems like you're trying to do). As Scott suggested, I recommend using a higher-order approach, where your Zod schema is an element of a higher-level type. I describe this pattern is more detail here. For use cases like yours (where you are presumably looping over Zod schemas to generate GraphQL schemas) it's a safer approach, since TypeScript can provide proper typing on your metadata properties. Without this, you usually need to cast to // assuming that I added a writable `metadata` field to the ZodType base class:
const User = z.object({ name: z.string() });
const Person = z.object({ age: z.number() });
const models = [User, Person];
for (const model of models) {
for (const fieldName of Object.keys(model.shape)) {
const field = (model.shape as any)[fieldName];
const name = field?.metadata?.graphqlMapping || fieldName; // TypeError;
// do stuff;
}
} Compare this to an "inverted" approach closer to what Scott suggested: type Field = { schema: z.ZodTypeAny; graphqlMapping?:string; }
type Model = {[k:string]: Field};
function model<Fields extends Model>(fields: Fields){
return fields;
}
const MyUser = model({
name: { schema: z.string(), graphqlMapping: "Name" },
age: { schema: z.number() },
});
const models: Model[] = [MyUser];
for(const model of models){
for(const [_fieldName, field] of Object.entries(model)){
field.schema instanceof z.ZodString;
field.graphqlMapping; // string | undefined
}
} As a workaround, you can still use intersection types to add typed metadata to Zod schemas without breaking Zod's compositionality: type Meta = { graphqlMapping: string };
type AnyObject = z.ZodTypeAny;
type AugmentSchema<T extends z.ZodTypeAny = z.ZodTypeAny> = T & Meta;
function augment<T extends AnyObject>(
schema: T,
metadata: Meta
): AugmentSchema<T> {
(schema as any).meta = metadata;
return schema as any;
}
const User = z.object({
name: augment(z.string(), {graphqlMapping:'Name'})
})
User.shape.name.graphqlMapping; // string; |
To answer your original question, there isn't a way to get a serializable representation of an arbitrary Zod schema. You could implement this yourself using the visitor pattern as described here: #335 (comment) |
Thanks @colinhacks @scotttrinh for the feedback here. Its great to get a deep (and timely!) answer like this! Points seem reasonable and make sense 👍 |
I know this is closed. But in your last example, does this typing work for the intersection to avoid casting of type Meta = { graphqlMapping: string };
type AnyObject = z.ZodTypeAny & { meta?: Meta };
function augment<T extends AnyObject>(schema: T, metadata: Meta): T {
schema.meta = metadata;
return schema;
} |
Hey,
Given a Zod schema e.g.:
Is there a way to get a plain data representation of this?
I am trying to map a Zod schema to a GraphQL schema (specifically an input object type).
The text was updated successfully, but these errors were encountered: