4

Some definitions exist for an API (types are generated using protocol buffers). I'd rather not touch these.

One of these types, lets call it SomeInterfaceOutOfMyControl has a property that is union type undefined. Like so:

interface SomeInterfaceOutOfMyControl {
    someProperty: number | undefined
}

function someFuncOutOfMyControl(obj: SomeInterfaceOutOfMyControl) {}

I am trying to write a validator to really assert that the data is in the correct format. I am using zod for this.

const validator = z.object({
    someProperty: z.optional(z.number()),
})

But the ts compiler doesn't seem to "understand" that number | undefined is the same thing as an optional property. So I get this compiler error:

error TS2322: Type '{ someProperty?: number | undefined; }' is not assignable to type 'SomeInterfaceOutOfMyControl'. Property 'someProperty' is optional in type '{ someProperty?: number | undefined; }' but required in type 'SomeInterfaceOutOfMyControl'.

const object: SomeInterfaceOutOfMyControl = validator.parse(someData)

const validator = z.object({
    someProperty: z.union([z.number(), z.undefined()]),
})
const someData = {} as any
const object = validator.parse(someData)

someFuncOutOfMyControl(object)
// Error on the line above:
// Argument of type '{ someProperty?: number | undefined; }' is not assignable to parameter of type 'SomeInterfaceOutOfMyControl'.
// Property 'someProperty' is optional in type '{ someProperty?: number | undefined; }' but required in type 'SomeInterfaceOutOfMyControl'.

How can I write the zod validation so that the inferred type becomes "correct" according to the interface? In other words, how do I write a validator for this?:

interface SomeInterfaceOutOfMyControl {
    someProperty: number | undefined
}

I tried using a union:

const validator = z.object({
    someProperty: z.union([z.number(), z.undefined()]),
})

But the result is the same...

birgersp
  • 3,909
  • 8
  • 39
  • 79
  • Does this answer your question? [Specify a Zod schema with a non-optional but possibly undefined field](https://stackoverflow.com/questions/71477015/specify-a-zod-schema-with-a-non-optional-but-possibly-undefined-field) – Souperman Jan 06 '23 at 03:33

2 Answers2

0

This is more of a workaround than a solution to the actual problem, which might not be applicable to what you're working with. But I thought I'd share it at least.

Since you can't edit SomeInterfaceOutOfMyControl.

You could extend it instead and make the fields optional with Partial. Like this:

interface SomeInterfaceOutOfMyControl {
  someProperty: number | undefined;
}

type MyInterface = Partial<SomeInterfaceOutOfMyControl>;

const validator: z.ZodType<MyInterface> = z.object({
  someProperty: z.union([z.number(), z.undefined()]),
});

const someData = {} as any;

const object: MyInterface = validator.parse(someData);
Felix Eklöf
  • 3,253
  • 2
  • 10
  • 27
  • 1
    But the error is still there. The reason you are not seeing it is because you defined `object` as an inferred type from the `validator`. If you explicitly specify `object` as a type of `SomeInterfaceOutOfMyControl` you should get the error. Try this: `const object: SomeInterfaceOutOfMyControl = validator.parse(someData);` and the error is still there – birgersp Jan 05 '23 at 09:47
  • You don't how to explicitly type it when using `ZodType`. If you remove `: SomeInterfaceOutOfMyControl` you will see that object has type `Partial` if you hover it. But i also update my answer to make it more clear. – Felix Eklöf Jan 05 '23 at 09:52
  • 1
    But the point of my question is that it needs to match the definition exactly. I need `object` to **explicitly** be of type `SomeInterfaceOutOfMyControl`. If it isn't then I wouldn't catch breaking changes in `SomeInterfaceOutOfMyControl` at compile-time... – birgersp Jan 05 '23 at 09:53
  • Oh wait maybe I misunderstood you, gimme a minute – birgersp Jan 05 '23 at 09:55
  • No, you're right. This is a workaround, I understand what you mean. It could work, but there is a difference between an object having a property that has value undefined vs. not having that property at all so there could be errors. – Felix Eklöf Jan 05 '23 at 09:57
  • 1
    I have updated my question. There are functions that take an argument of type `SomeInterfaceOutOfMyControl`, where I can't use the `object` variable, since the type definitions are misaligned. Really appreciate you trying to help out btw :) – birgersp Jan 05 '23 at 10:02
0

Would adding a tranform to the schema solve your problem:

import {z} from "zod"
interface SomeInterfaceOutOfMyControl {
    someProperty: number | undefined
}

function someFuncOutOfMyControl(obj: SomeInterfaceOutOfMyControl) {}

const validator = z.object({
    someProperty: z.number().optional(),
}).transform(o => ({...o, someProperty: o.someProperty }))

const someData = {} as unknown
const object: SomeInterfaceOutOfMyControl = validator.parse(someData)
someFuncOutOfMyControl(object)
Vulwsztyn
  • 2,140
  • 1
  • 12
  • 20