Tango
Tango
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
Thats fine, I guess either way, we need a bunch of dynamic code, if we like it to behave as desired. I think within code generation we have some options, but nothing really streight forward. Thanks again.
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
yeah. Maybe this is not something zod can / should solve. Could be very specific for "deserialization". Thanks for your insights!
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
oh, sry, nothing really. I just can't get it working like I would need it.
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
const UnknownPet = z
.object({ type: z.string() })
.passthrough()
.transform((v) => ({
type: "UnknownPet" as const,
object: v as unknown,
}));
const UnknownPet = z
.object({ type: z.string() })
.passthrough()
.transform((v) => ({
type: "UnknownPet" as const,
object: v as unknown,
}));
From your example, you would rather try to transform to a value we know at compile time. I think, this is also the solution for several client / server code generators for their deserializers. But all obviously need some "engine" or transformation logic to work the unknown type out. For Javascript I heaven't really seen anything yet.
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
test("should fail for invalid known schema - discriminated Union", () => {
const Pet = z.discriminatedUnion("type", [Cat, Dog]).or(UnknownPet);
const result = Pet.safeParse({ type: "Cat" });
expect(result.success).toEqual(false);
});
test("should fail for invalid known schema - discriminated Union", () => {
const Pet = z.discriminatedUnion("type", [Cat, Dog]).or(UnknownPet);
const result = Pet.safeParse({ type: "Cat" });
expect(result.success).toEqual(false);
});
unfortunately, this is not good enough. I had similar thoughts. It will pass also for schemas, we know are incorrect.
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
describe("zod", () => {
test("should fail for invalid known schema", () => {
const Pet = z.union([Cat, Dog, UnknownPet]);
const result = Pet.safeParse({ type: "Cat" });
expect(result.success).toEqual(false);
});
});
describe("zod", () => {
test("should fail for invalid known schema", () => {
const Pet = z.union([Cat, Dog, UnknownPet]);
const result = Pet.safeParse({ type: "Cat" });
expect(result.success).toEqual(false);
});
});
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
wouldn't a discrimnatedUnion already have blocked any unknown option?
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
interesting
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
yep, this is also an issue - with some intersection magic or tagged / opaque typing however, to me at least somewhat solvable
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
yeah, error handling was something which I also thought of.
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
Currently I try to solve the issue with a z.custom logic. It is a bigger example, but I explan it with a little more detail here: https://github.com/dasaplan/ts-mono/blob/main/packages/openapi-tolerant-reader/docs/a_solution_for_typesafe_parsing_the_unknown.md#solution-creating-a-custom-zod-schema
const PetMatcher = {Cat: Cat, Dog: Dog, onDefault: UnknownPet} as const;
const Pet = createTolerantPetSchema("type", PetMatcher);

type TPetMatcher = typeof PetMatcher;

export function createTolerantPetSchema(discriminator: "type", matcher: TPetMatcher) {
return z.custom((val) => {
if (typeof val !== "object" || val === null) {
// invalid payload
return false;
}
if (!(discriminator in val) || typeof val[discriminator] !== "string") {
// invalid payload, invalid discriminator
return false;
}
const discriminatorValue = val[discriminator];
const schema = discriminatorValue in matcher ? matcher[discriminatorValue as keyof TPetMatcher] : matcher["onDefault"];
const parsed = schema.safeParse(val);
return parsed.success ? parsed.data : false;
});
const PetMatcher = {Cat: Cat, Dog: Dog, onDefault: UnknownPet} as const;
const Pet = createTolerantPetSchema("type", PetMatcher);

type TPetMatcher = typeof PetMatcher;

export function createTolerantPetSchema(discriminator: "type", matcher: TPetMatcher) {
return z.custom((val) => {
if (typeof val !== "object" || val === null) {
// invalid payload
return false;
}
if (!(discriminator in val) || typeof val[discriminator] !== "string") {
// invalid payload, invalid discriminator
return false;
}
const discriminatorValue = val[discriminator];
const schema = discriminatorValue in matcher ? matcher[discriminatorValue as keyof TPetMatcher] : matcher["onDefault"];
const parsed = schema.safeParse(val);
return parsed.success ? parsed.data : false;
});
31 replies
ZZod
Created by Tango on 5/7/2025 in #questions
Tango - I have the need to handle discriminated...
Discriminated unions in TypeScript don't work that way, so discriminated unions in Zod don't either. 😅
I actually agree ^^.
What should you do in this case? Well, that is up to your logic
Exactly. Working with Zod on API / Integration layers, we need be able to let the application layer deal with the unknown. However, there is also the need to parse as strict as possible with well defined error messages.
31 replies