Search code examples
typescriptzod

How to implement "Property in keyof Type" shape with Zod?


I have a Zod shape with quite lots of keys. I need another shape with the same keys, but different types. With plain Typescript I could define them such as

type TypeA = {
  something1: number
  something2: number
  ...
  somethingN: number
}

type TypeB = {
  [Property in keyof TypeA]: string
}

and then whenever I need to add or remove keys I only need to modify the TypeA. I would like to recreate this with Zod, so that I would get shape/parser for both types. Is that possible and if yes, how can I do that?

This wont work as it only gives me the shape for the first type

const TypeA = z.object({
  something1: z.number(),
  something2: z.number(),
  ...
  somethingN: z.number()
)}

type TypeA = z.infer<typeof TypeA>

type TypeB = {
  [Property in keyof TypeA]: string
}
// No shape for TypeB

Is there a way in zod to iterate over the keys and assign them some type (or is it still called shape before the type is inferred?)


Solution

  • // declare property list once here, as string const string array.
    const PropertyKeysNames = ["something1", "something2", "somethingN"] as const satisfies readonly string[];
    type PropertyKeys = typeof PropertyKeysNames[number];
    
    // create both your TypeA and TypeB, using indexed, as you done.
    type TypeA = {[Property in PropertyKeys]: number;};
    type TypeB = {[Property in PropertyKeys]: string;};
    
    // Now you need an instance of zod schema, not a type.
    // we dynamically create it at runtime 
    
    /**
     * create a zod object with PropertyKeysNames as keys, each has type of a zod type in parameter
     * @param zobject the zod object of each properties, typically z.string()
     * @returns same a z.object(...) call
     */
    const build = (zobject: z.ZodTypeAny): ReturnType<typeof z.object> => {
        const shape: { [k: string]: z.ZodTypeAny } = {};
        for (const key of PropertyKeysNames) {
            shape[key] = zobject;
        }
        return z.object(shape);
    };
    
    // The result
    const ZobjectWithString = build(z.string());
    

    tests :

    const ObjectsToTest = [
        {something1: "something", something2: "something else", somethingN: "bob l'eponge"}, // OK
        {something1: "something", something2: "something else", somethingN: "bob l'eponge", somethingMORE:"more"}, // OK
        {somethingN: "bob l'eponge"}, // failed
        { username: "Ludwig" }, // failed
        undefined
    ]
    let i = 0;
    for(const o of ObjectsToTest){
        try {
            ZobjectWithString.parse(o);
            console.log("test " + i + " ok");
        }catch(e){
            console.log("test " + i + " failed");
        }
        ++i;
    }
    

    Hopes that could help