1

I would like to have a generic type predicate that allows me to check the type of an object's property.

To illustrate this, I want to achieve the following:

const a: { [key in string]: string | string[]} = {
  arrayProp: ["this", "is", "an", "array"],
  otherProp: "this is a string"
};
Object.keys(a).forEach((prop: keyof typeof a) => {
  if (isArrayProperty(a, prop)) {
    // `a[prop]` is correctly detected as array
    a[prop].push("another value")
  }
})

I was expecting something like this to work

function isArrayProperty<T, P extends keyof T>(
  obj: T,
  prop: P,
): T[P] is Array<any> {
  return Array.isArray(obj[prop]);
}

However TypeScript seems to have problems with the generics and is statement in the return type.

Additional notes

I know I could just pass the value to a function like Array.isArray(a["arrayProp"]) to get it work. However, I want to go even one step further where I pass in a constructor and a property to see whether the property of an object is an array type:

type ClassConstr<T> = new (...props) => T

function isArrayProperty<T, P extends keyof T>(
  obj: ClassConstr<T>,
  prop: P,
): T[P] is Array<any> {
  return // some magic happening here;
}

class A {
  someProp: any;
}

const a = new A()
a = ["some", "array"];

if (isArrayProperty(A, "someProp") {
  // `a.someProp` is correctly detected as array
  a.someProp.push("new value");
}

The background is, that I have a separate schema definition for my classes that is only available at runtime. These schema definitions then decide, whether a property is an array, a string, a date, ... Therefore, I would like to have a function that allows me to still achieve type-safety in the components where I use these classes.

1 Answer 1

1

Consider this:

const a: { [key in string]: string | string[] } = {
  arrayProp: ["this", "is", "an", "array"],
  otherProp: "this is a string"
};

function isArrayProperty<T, P extends keyof T>(
  obj: T,
  prop: P,
): obj is T & Record<P, Array<any>> {
  return Array.isArray(obj[prop]);
}

Object.keys(a).forEach((prop: keyof typeof a) => {
  if (isArrayProperty(a, prop)) {
    a[prop].push("another value")
  }
})

Playground

You need to assure TS that obj in isArrayProperty is a record with prop P and value Array<any>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.