Infer type from key of object inside an array Zod
Asked Answered
B

1

8

So I'd like to grab the type from a key of an object within an array in Zod. That array is also nested within an object, just to make things extra difficult.

This is an abstract view of the problem I'm having:

const obj = z.object({
  nestedArray: z.array(z.object({ valueIWant: z.string() }))
})

// Should be of type z.ZodArray() now, but still is of type z.ZodObject
const arrayOfObjs = obj.pick({ nestedArray: true })

// Grab value in array through z.ZodArray().element
arrayOfObjs.element.pick({ valueIWant: true })

What should happen using arrays in Zod:

// Type of z.ZodArray
const arr = z.array(z.object({ valueIWant: z.string() }))

const myValue = arr.element.pick({ valueIWant: true })

Here is my actual problem:

I have an API which returns the following object:

export const wordAPI = z.object({
  words: z.array(
    z.object({
      id: z.string(),
      word: z.string(),
      translation: z.string(),
      type: z.enum(['verb', 'adjective', 'noun'])
    })
  )
})

In my tRPC input, I would like to allow filtering by word type. Right now, I've had to rewrite z.enum(['verb', 'adjective', 'noun']), which isn't great as it could introduce problems later on. How can I infer the type of the word through the array?

tRPC endpoint:

export const translationsRouter = createRouter().query('get', {
  input: z.object({
    limit: z.number().default(10),
    avoid: z.array(z.string()).nullish(),
    wordType: z.enum(['verb', 'adjective', 'noun']).nullish() // <-- infer here
  }),
  [...]
})
Bussy answered 19/7, 2022 at 20:37 Comment(0)
T
11

The way I would suggest doing this is to pull out the wordType field as a separate schema which you would then use within the z.object. Like:

const wordTypeSchema = z.enum(["verb", "adjective", "noun"]);
type WordType = z.infer<typeof wordTypeSchema>;
export const wordAPI = z.object({
  words: z.array(
    z.object({
      id: z.string(),
      word: z.string(),
      translation: z.string(),
      type: wordTypeSchema
    })
  )
});

and then I would just use the WordType type wherever else I needed just that value.

It is, however, possible to extract the type from your nested object like so:

type WordAPI = z.infer<typeof wordAPI>;
type WordType = WordAPI['words'][number]['type'];
//    ^- This will include `| null` because you used `.nullable`
// If you don't want the | null you would need to say
type WordTypeNotNull = Exclude<WordType, null>;

Again, I would recommend the first approach as it's more reusable and less susceptible to needing to be updated if the object changes shape for example.

Tomaso answered 21/7, 2022 at 2:30 Comment(3)
I thought it'll have been better to infer the type the complicated way, but I do it the way you recommended. ty :)Bussy
there is no z.TypeOf in the current version (3.21.4) of the libDistance
Interesting, I wonder why they took it out. I updated my answer to use z.infer which was the other optionTomaso

© 2022 - 2024 — McMap. All rights reserved.