is icon indicating copy to clipboard operation
is copied to clipboard

Proposal: validate type of object properties

Open lo1tuma opened this issue 4 years ago • 4 comments

I would like to propose a new function is.propertyOf(object: unknown: key: string, predicate: Predicate): boolean that accepts 3 values and checks:

  1. wether the given object value is an object and
  2. if the given object has an own property with the name key and
  3. that the value of the specified property matches the given predicate

Example Usage:

is.propertyOf(foo, 'bar', is.string);

Why

Given the following example

interface Foo {
  bar?: string;
  baz: number
}

function doStuff(foo?: Foo) {
  if (is.string(foo?.bar)) { // using optional chaining because `foo` might be undefined
    console.log(foo.baz); // typescript still things `foo` could be undefined
  }
}

Unfortunately typescript is not smart enough to understand that within the if block foo is always defined.

Let’s say is.propertyOf is implemented similar to this:

function propertyOf<O extends unknown, K extends keyof Exclude<O, undefined>, P>(obj: O, key: K, predicate: Predicate<P>): obj is Exclude<O, undefined> & Record<K, P> {
  return true;
} 

then the code from above could look like this:

interface Foo {
  bar?: string;
  baz: number
}

function doStuff(foo?: Foo) {
  if (is.propertyOf(foo, 'bar', t.string)) {
    console.log(foo.bar); // typescript now knows two things: foo is an object and its property bar is a string
  }
}

lo1tuma avatar Sep 27 '21 10:09 lo1tuma

Are you aware of any TypeScript issues about it? Would you be able to link some?

From a quick search, https://github.com/microsoft/TypeScript/pull/38839 looks relevant.

sindresorhus avatar Sep 27 '21 11:09 sindresorhus

I haven’t really checked the TypeScript issues before. It looks like that microsoft/TypeScript#38839 could fix this problem. I’ve also found this issue https://github.com/microsoft/TypeScript/issues/34974.

lo1tuma avatar Sep 27 '21 11:09 lo1tuma

It seems likely to be extended to a function that verify the schema of an object.

Some ideas are as follows.

import is from '@sindresorhus/is'
import {objectEntries, objectHasOwn} from 'ts-extras';

const isInterface = <ObjectType extends Record<string, unknown>>(
  value: unknown,
  interface_: {
    [Key in keyof ObjectType]: (value: unknown) => value is ObjectType[Key];
  },
): value is ObjectType => {
  return objectEntries(interface_).every(
    ([key, predicate]) => objectHasOwn(value, key) && predicate(value[key]),
  );
};

declare const someObject: unknown;

if (
  isInterface(someObject, {
    foo: is.string,
    bar: is.number,
    baz: is.boolean,
  })
) {
  someObject;
  // {
  //     foo: string;
  //     bar: number;
  //     baz: boolean;
  // }
}

TypeScript Playground

younho9 avatar Dec 14 '21 14:12 younho9

There is a similar implementation in ow. https://github.com/sindresorhus/ow/issues/92

younho9 avatar Dec 15 '21 04:12 younho9