top | item 44031272

(no title)

stephen_hn | 9 months ago

I think you would use discrimated unions.

const MyResult = z.discriminatedUnion("status", [ z.object({ status: z.literal("success"), data: z.string() }), z.object({ status: z.literal("failed"), error: z.string() }), ]);

You can define passthrough behavior if there are a bunch of special attributes for a moderator but you don't want to list/check them all.

With different methods that have different schema- If they share part of the same schema with alterations, you can define an object for the shared part and create objects that contain the shared object schema with additional fields.

If you have a lot of different possibilities, it will be messy, but it sounds like it already is for you, so validators could still at least validate the messines.

discuss

order

johnfn|9 months ago

Hmm, this is pretty interesting, but I still have issues. While Zod seems to be able to transform my server-returned object into a discriminated union, it doesn't seem to be able to tell which of the unions it is. For instance, say that api/user/mod-panel (e.g.) returns only moderator user objects. My API endpoint already knows that it will have moderator-related fields, but Zod forgets that when I parse it.

Scarblac|9 months ago

Then you don't use the discriminated union for that endpoint, but the schema that only accepts moderator user objects.

That's not surprising, "this endpoint will only return moderator user objects" is a bit of knowledge that has to be represented in code somehow.

vjerancrnjak|9 months ago

You can use an ‘is’ method to pick out a type from union. Although, if union is discriminated, it’s discriminated with a ‘kind’ so you should be able to know which kind you received.

stephen_hn|9 months ago

I am not totally sure what your API's look like but if you know the API only has a certain type (or subset of types), you would only validate that type on the method. Don't use a single union for everything.

Here is some pseudocode.

Person = { name: string, height: number }

Animal = {name: string, capability: string}

A = { post: object, methodType: string, person: Person }

ModeratorA = { post: object, moderatorField1: string, moderatorField2: string, person: Person }

UnionA = A && ModeratorA (There's probably a better way of defining A and ModeratorA to share the shared fields)

B = { post: object, animal: Animal }

endpoint person parses UnionA

endpoint animal parses B

You don't put all of your types in one big Union.

Sammi|9 months ago

You can define all the different sub types separately and make the discriminated union using them. The use only a single type for an endpoint if it only uses a single type. Only use the discriminated union where you actually might be handling multiple types.