Igor Loskutov
Igor Loskutov

Reputation: 2325

How to map the field names of a Zod Discriminated Union

Given a discriminated union

export const duParser = z.discriminatedUnion('type', [
  z.object({
    type: z.literal('a'),
    id: aParser,
  }),
  z.object({
    type: z.literal('b'),
    id: bParser,
  }),
]);

z.infer would be {type: 'a', id: A} | {type: 'b', id: B}

Now, I'd like to procure a parser and type out of it, such as:

  1. The union field (type) is mapped to another name (i.e. type -> type2): {type2: 'a', id: A} | {type2: 'b', id: B}

  2. Other fields (although I agree it's bigger trouble, hence this division in the question) are mapped i.e. {type: 'a', id2: A} | {type: 'b', id2: B}

The specific use case is that I have a generic parser into {type: ..., id: ...} which I'd like to merge with a flat structure parser, giving the discriminated union prefixed fields. The parser being flat is the requirement of the use case, where the parsed structure is a http query string: rootField1=value1&rootField2=value2&subfieldId=subValue1&subfieldType=a where subfieldId and subfieldType are "id" and "type" of my original discriminatedUnion. Keeping them "id" and "type" isn't desirable since these attributes belong to another level of abstraction (i.e. the root entity could have its own id and type).

Normally, I would do a nested structure here, but I wonder if flattening it together with mapping field names is possible in Zod.

Upvotes: 0

Views: 5872

Answers (1)

Souperman
Souperman

Reputation: 9836

If I understand the question correctly I think both of the things you're trying to do are possible with the transform function.

You might need to define the discriminated union type you're trying to transform into, however.

// Using number and string as stand-in types for your a and b parsers

interface IMappedA {
  type2: 'a',
  id2: number;
}

interface IMappedB {
  type2: 'b',
  id2: string;
}

type Mapped = IMappedA | IMappedB;

With this type you can perform a transform into this new discriminated union like:

const mappedUnionSchema = z.discriminatedUnion('type', [
  z.object({
    type: z.literal('a'),
    id: z.number(),
  }),
  z.object({
    type: z.literal('b'),
    id: z.string(),
  }),
]).transform((input): Mapped => {
  if (input.type === 'a') {
    return { type2: 'a', id2: input.id };
  } else {
    return { type2: 'b', id2: input.id };
  }
});

Upvotes: 2

Related Questions