guidance following removal of intersection
See original GitHub issueHi, with the removal of intersection
, I’m wondering what the best way to represent base shapes with conditional extensions? From looking at the diff, I suppose the recommendation is to use merge
or extend
, but that isn’t very ergonomic with union types…
For example, I have a schema that validates oidc-provider client configurations that looks like this:
const clients = array(
intersection(
object({
client_id: string(),
grant_types: array(string()),
redirect_uris: array(string()),
tos_uri: string().url(),
policy_uri: string().url(),
}),
union([
object({
application_type: literal("native"),
token_endpoint_auth_method: literal("none"),
}),
object({
token_endpoint_auth_method: z
.enum(["client_secret_basic", "client_secret_post"])
.optional(),
client_secret: string(),
}),
object({
token_endpoint_auth_method: z.enum(["private_key_jwt"]),
jwks: object({
keys: array(
object({
kty: string(),
kid: string(),
n: string(),
e: string(),
})
),
}),
}),
])
)
)
It’s a little busy, but you can see that there’s a base schema that intersects with a union type for all of the conditional variants.
Without intersect
, it seems like every type in the union has to be explicitly extended:
const clientBase = {
client_id: string(),
grant_types: array(string()),
redirect_uris: array(string()),
tos_uri: string().url(),
policy_uri: string().url(),
};
const clients = array(
union([
object({
application_type: literal("native"),
token_endpoint_auth_method: literal("none"),
}).extend(clientBase),
object({
token_endpoint_auth_method: z
.enum(["client_secret_basic", "client_secret_post"])
.optional(),
client_secret: string(),
}).extend(clientBase),
object({
token_endpoint_auth_method: z.enum(["private_key_jwt"]),
jwks: object({
keys: array(
object({
kty: string(),
kid: string(),
n: string(),
e: string(),
})
),
}),
}).extend(clientBase),
])
);
We can’t use [].map()
over the union argument because map
doesn’t preserve the input length in the output type.
Is there another way of continuing to express this, or is the expectation that every entry in a union has to be manually extended?
Issue Analytics
- State:
- Created 3 years ago
- Comments:6 (4 by maintainers)
Top GitHub Comments
Okay, this issue, combination with #351, have convinced me that it’s worthwhile to keep ZodIntersection.
I’ve added it back and also added a
and
method to the base class as a way to easily define intersection types. If you change all your calls ofA.merge(B)
toA.and(B)
, your code should continue working as it did before the alpha.7 release. You’ll need to upgrade to the latest alpha to get the.and
method:yarn add zod@next
cc @jstewmonNote that
.merge
will still allow key overwriting (like.extend
).That works when
Out
is the same across all items in the tuple, but not if the type of an individual element in the output tuple varies based on the type of that element in the input.Here’s my best attempt (doesn’t work)
You still end up with a union type for each element: