admin管理员组

文章数量:1355542

Casting the generic type to any works (The type E will be a typescript type, class, or interface) of some entity like Product, Post, Todo, Customer, etc.:

function test<E>(o:E):string {
  return (o as any)['property']
}

Just wanted to see whether casting to any is generally how this should be handled?

The full context was requested. Here's is the entire function being implemented:

/**
 * 
 * @param entities The entities to search
 * @param exclude Keys to exclude from each entity
 * 
 * @return E[] Array of entities with properties containing the search term.
 */
export function search<E extends WithProperty>(query:string='', entities:E[], exclude:string[]=[]) {
  const { isArray } = Array

  query = query.toLowerCase();

  let keys:string[] = []
  if (entities.length > 0) {
    keys = excludeKeys(entities[0], exclude)
  }

  return entities.filter(function (e:E) {
    return keys.some((key)=>{
      const value = (e as any)[key];
      if (isArray(value)) {
        return value.some(v => {
          return new String(v).toLowerCase().includes(query);
        });
      }
      else if (!isArray(value)) {
        return new String(value).toLowerCase().includes(query);
      }
    })
  });
}

/**
 * The method can be used to exclude keys from an instance
 * of type `E`.  
 * 
 * We can use this to exclude values when searching an object.
 * 
 * @param entity An instance of type E
 * @param eclude  The keys to exclude
 * 
 */
export function excludeKeys<E>(entity: E, exclude: string[]) {
  const keys: string[] = Object.keys(entity);
  return keys.filter((key) => {
    return exclude.indexOf(key) < 0;
  });
}


Casting the generic type to any works (The type E will be a typescript type, class, or interface) of some entity like Product, Post, Todo, Customer, etc.:

function test<E>(o:E):string {
  return (o as any)['property']
}

Just wanted to see whether casting to any is generally how this should be handled?

The full context was requested. Here's is the entire function being implemented:

/**
 * 
 * @param entities The entities to search
 * @param exclude Keys to exclude from each entity
 * 
 * @return E[] Array of entities with properties containing the search term.
 */
export function search<E extends WithProperty>(query:string='', entities:E[], exclude:string[]=[]) {
  const { isArray } = Array

  query = query.toLowerCase();

  let keys:string[] = []
  if (entities.length > 0) {
    keys = excludeKeys(entities[0], exclude)
  }

  return entities.filter(function (e:E) {
    return keys.some((key)=>{
      const value = (e as any)[key];
      if (isArray(value)) {
        return value.some(v => {
          return new String(v).toLowerCase().includes(query);
        });
      }
      else if (!isArray(value)) {
        return new String(value).toLowerCase().includes(query);
      }
    })
  });
}

/**
 * The method can be used to exclude keys from an instance
 * of type `E`.  
 * 
 * We can use this to exclude values when searching an object.
 * 
 * @param entity An instance of type E
 * @param eclude  The keys to exclude
 * 
 */
export function excludeKeys<E>(entity: E, exclude: string[]) {
  const keys: string[] = Object.keys(entity);
  return keys.filter((key) => {
    return exclude.indexOf(key) < 0;
  });
}


Share Improve this question edited Dec 11, 2019 at 20:24 Ole asked Dec 11, 2019 at 19:29 OleOle 47.4k70 gold badges237 silver badges445 bronze badges 9
  • 1 Does this really constitute a minimal reproducible example? What is the use case here? Do you expect that o will always have a string-valued property at the key "property"? If so, then function test<E extends {property: string}>(o: E): string { return o.property } should work, but so would function test(o: {property: string}) { return o.property } with no generics at all. So I'm confused about what your intent is here. Good luck! – jcalz Commented Dec 11, 2019 at 19:34
  • This is the use case. Implementing the search in typescript ATM : medium./@ole.ersoy/… – Ole Commented Dec 11, 2019 at 19:45
  • The object o is just an entity (Customer, Product, etc.) of generic type E ... in other words it will be Typescript class or interface or type instance which IIUC always has a string as the property key. – Ole Commented Dec 11, 2019 at 19:47
  • I think the extends is elegant, but it does not look like it works in this case since the property must be accessed using []. – Ole Commented Dec 11, 2019 at 19:53
  • 1 That attached code has errors unrelated to the question you're asking. I'd possibly suggest that keys should be of type Array<keyof E> instead of string[], and then go from there, since if you have a value o of type E and a value k of type keyof E then o[k] will pile without needing to assert o to be any. Without agreement on what constitutes a true minimal reproducible example I can't proceed further, though. Hopefully you get the answer you seek! – jcalz Commented Dec 11, 2019 at 20:10
 |  Show 4 more ments

1 Answer 1

Reset to default 6

If you know the type constraint has a property named property you can define an interface which defines the property and then use a constraint that tell E extends it. Then you will have access to that property without casting it.

interface WithProperty{
  property:string;
}

function test<E extends WithProperty>(o:E):string {
  return o.property;  // or o["property"] is valid access.
}

Playground

Edit : Since you updated your example. There is another way of doing that which is to use keyword keyof. Also using this one doesn't require knowledge of the properties. I have modified your example like below :

export function search<E>(query:string='', entities:E[], exclude:string[]=[]) {
  const { isArray } = Array
  type EKey = keyof E;
  query = query.toLowerCase();

  let keys : EKey[] = []
  if (entities.length > 0) {
    keys = excludeKeys<E>(entities[0], exclude)
  }

  return entities.filter(function (e:E) {
    return keys.some((key =>{
      const value = e[key];
      if (isArray(value)) {
        return value.some(v => {
          return v.toLowerCase().includes(search);
        });
      }
      else if (!isArray(value)) {
        return new String(value).toLowerCase().includes(query);
      }
    })
  });
 }

For the excludeKeys part of the code casting bees inevitable because of this looong going discussion.

export function excludeKeys<E>(entity: E, exclude: string[]) {
 const keys: string[] = Object.keys(entity);
  return <(keyof E)[]>keys.filter((key) => {
    return exclude.indexOf(key) < 0;
   });
}

Playground

本文标签: javascriptAccessing properties of generic types in typescriptStack Overflow