ow icon indicating copy to clipboard operation
ow copied to clipboard

Is there a way for an object predicate to differentiate between key with value `undefined`, and key not present?

Open atomanyih opened this issue 2 years ago • 2 comments

If we want to validate an object with optional parameters for a spread:

type Hotdog = {
  length: number;
  topping: string;
}

function patchHotdog(hotdog: Hotdog, patchBody: unknown): Hotdog {
  ow(
    patchBody,
    ow.object.exactShape({
      length: ow.optional.number,
      topping: ow.optional.string,
    })
  )
  
  return {
    ...hotdog,
    ...patchBody
  }
  // ERROR:
  //TS2322: Type '{ length: number | undefined; topping: string | undefined; }' is not assignable to type 'Hotdog'.
  //   Types of property 'length' are incompatible.
  //     Type 'number | undefined' is not assignable to type 'number'.
  //       Type 'undefined' is not assignable to type 'number'.
}

This doesn't work, as ow will assert patchBody as

type ValidatedPatchBody = {
  length: number | undefined;
  topping: string | undefined;
}

when really we want

type ValidatedPatchBody = {
  length?: number;
  topping?: string;
}

Which makes a difference, as js treats undefined value and no key differently when spreading:

const obj1 = { bar: 'foo', ...{} } // => { bar: 'foo' }
const obj2 = { bar: 'foo', ...{ bar: undefined } } // => { bar: undefined }

Unless I'm missing something, it doesn't look like ow provides a way to do this out of the box.


In the mean time, I've worked around this by passing in an explicit type argument:

function patchHotdog(hotdog: Hotdog, patchBody: unknown): Hotdog {
  ow<{length?: number, topping?:string}>(
    patchBody,
    ow.object.exactShape({
      length: ow.optional.number,
      topping: ow.optional.string,
    })
  )

  return {
    ...hotdog,
    ...patchBody
  } // IT OKAY
}

atomanyih avatar Mar 10 '22 01:03 atomanyih

Unless I'm missing something, it doesn't look like ow provides a way to do this out of the box.

Correct. .optional indicates the value is optional, not the key.

sindresorhus avatar Mar 19 '22 17:03 sindresorhus

I think this use-case should be supported, but I don't have a clear idea on how right now.

sindresorhus avatar Mar 19 '22 17:03 sindresorhus