question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Request for help: typed Zod combinator with dynamic field name

See original GitHub issue

I apologize, this is almost certainly not the correct place to ask, but I’m not sure where else.

I asked this question on Stack Overflow, reproducing it below.


My XML to JSON library emits {MyKey: T} for one-element lists, and {MyKey: T[]} for multi-element lists. The corresponding TypeScript type is type XmlJsonArray<T, element extends string> = Record<element, T | T[]>. I’ve used the following to implement it as a Zod schema:

const XmlJsonArray = <T, element extends string>(element: element, schema: z.Schema<T>) => {
  // TODO what is the idiomatic approach here?
  const outerSchema: Record<element, z.Schema<T | T[]>> = {} as any;
  outerSchema[element] = z.union([schema, z.array(schema)]);
  return z.object(outerSchema);
};

Is there a way to do this without using any?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:14 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
colinhackscommented, Nov 27, 2020

So you want to explicitly annotate the return type of the function? Any particular reason?

In general things get funky when you use z.ZodType as a return type since it’s an abstract base class that isn’t really intended for actual use. At some point I need to write up a whole guide about building generic functions on top of Zod that walk through some of these best practices.

The actual way to type this requires more intimate knowledge of Zod. You need to tell TypeScript the full structure of the schema that gets returned from your function. Here’s how to do it:

import * as z from '.'

const XmlJsonArray = <T extends string, S extends z.ZodType<any>>(
  tag: T,
  schema: S,
): z.ZodObject<{ [k in T]: z.ZodUnion<[S, z.ZodArray<S>]> }> => {
  return z.object({}).setKey(tag, z.union([schema, z.array(schema)]));
};

const mySchema = XmlJsonArray('tuna', z.string());
type mySchema  = z.infer<typeof mySchema>
// => { tuna: string | string[]; }

But I wouldn’t recommend this. If you change your implementation you’ll need to update the type signature accordingly. Best to let the type inference engine work it’s magic here.

Note: I made an important change to your code. When using generics, don’t do this:

const myFunc = <T>(schema: z.Schema<T>)=>{
  // ...
}

instead do this:

const myFunc = <T extends z.Schema<any>>(schema: T)=>{
  // ...
}

This lets TypeScript infer the exact subclass of ZodType (e.g. ZodString, ZodObject, etc) instead of treating everything as an instance of ZodType (the base class for all Zod schemas).

1reaction
scotttrinhcommented, Nov 24, 2021

No particular reason other than we just didn’t write anything about it! I’ll write something up later today, thanks for the encouragement.

Read more comments on GitHub >

github_iconTop Results From Across the Web

typed Zod combinator with dynamic field name · Issue #211 ...
I've used the following to implement it as a Zod schema: const XmlJsonArray = <T, element extends string>(element: element, schema: z.Schema<T>) => {...
Read more >
Typed Zod combinator with dynamic field name
Asking for help, clarification, or responding to other answers. · Making statements based on opinion; back them up with references or personal ...
Read more >
Dynamic Field names?
I have a log that looks like this: cs1Label="Field Name" ... there a way to dynamically parse out the cs# values and give...
Read more >
Parse, Don't Validate (2019)
This argument is common, but I've never understood how a dynamically typed language is supposed to avoid coupling algorithms to data structures.
Read more >
Data validation with automatic typing using zod
Validating data without pain using the zod library. ... After running this code, we will get an error because the user object is...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found