3

I'm thinking about the following validation with zod and I have no clue on how to do it (or if it's possible with zod). I want an array of objects, all with the same shape, with some of them with literal props, I need these always present in the array.

Example: I need always in the array the objects those with name required1 and required2, and then other objects optionals following the same shape.

[
    {
      name: z.literal('required1'),
      otherprop: z.number()
    },
    {
      name: z.literal('required2'),
      otherprop: z.number()
    },
    // I want to include one or more of the following too (optionals).
    {
      name: z.string(),
      otherprop: z.number()
    },
]

This other example needs to throw because required2 is missing

[
    {
      name: z.literal('required1'),
      otherprop: z.number()
    },
    // I want to include one or more of the following too.
    {
      name: z.string(),
      otherprop: z.number()
    },
]

Any clue?

2
  • 2
    @Yunnosch Thank you for the clarification and the link provided! And sorry for the inconvenience. Commented Nov 8, 2021 at 13:51
  • If you see my point then it was a minor effort with good effect. Have fun. Commented Nov 8, 2021 at 13:52

1 Answer 1

5

There is no way to solve the problem from just the typing system. I solved the issue using the refine method from Zod. I will post two versions, one simpler with refine and other more complex with superRefine.

Base code

const elementSchema = z.object({
  name: z.string(),
  otherprop: z.number(),
})
type Element = z.infer<typeof elementSchema>;

// In my real code, here I have a function that returns the array of required names for the case.
const names = ['required1', 'required2'];

function refineNames(elements: Element[]): boolean {
       return names.every((el: string) => elements.some(x => x.name === el));
}

Simple way using just refine

z.array(elementSchema).refine(
  (elements) => refineNames(elements),
  { message: `There are missing names. Required names are ${names.join(', ')}`, }
);

Complex way using superRefine. But we can compare also for duplicate entries.

function hasDuplicates(elements: Element[]): boolean {
    const names = elements.map(e => e.name);

    return names.length !== new Set(names).size;
}

z.array(elementSchema).superRefine((elements, ctx) => {
        if (refineNames(elements)) {
            ctx.addIssue({
                code: z.ZodIssueCode.custom,
                message: `There are missing names. Required names are ${names.join(', ')}`,
            });
        }

        if (hasDuplicates(elements)) {
            ctx.addIssue({
                code: z.ZodIssueCode.custom,
                message: 'No duplicated name allowed.',
            });
        }
    }),

References:

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.