2

I created a function that receives a string keys based object with any desired value.

The function definition line:

export const pickFromObj = <
  Obj extends { [K: string]: unknown }, 
  PickKeys extends keyof Obj
>(object: Obj, keys: PickKeys[]) => {
  1. First I would like to know if there is any way to make this shorter.

  2. In addition, when I pass some the following object to the function, I receive the following error from TS compiler:

Argument of type 'IExample' is not assignable to parameter of type '{ [x: string]: unknown; }'.
  Index signature is missing in type 'IExample'.ts(2345)

IExample:

export interface IExample {
  fromAddress: string;
  toAddress: string;
  status: number;
}
Ron Rofe
  • 738
  • 1
  • 9
  • 25

3 Answers3

3

This is because { [key: string]: unknown } means that the object has an index signature. So you can use any string as a key for this object, while IAreasInfoFormFields does not have index signature.

Consider this example:

type Indexed = { [K: string]: unknown }

interface NotIndexed {
  name: string;
}

const foo = <T extends Indexed>(obj: T) => obj

const obj: NotIndexed = {
  name: 'name',
}

foo(obj) // error

Here you see an error because you are not allowed to do smth like this:

interface NotIndexed {
  name: string;
}

const obj: NotIndexed = {
  name: 'name',
}

obj.hello // error

But if you have indexed object, you are allowed to use any string key:

type Indexed = { [K: string]: unknown }

const obj: Indexed = {
  name: 'name',
}

obj.hello // ok

One interesting thing, that types are indexed by the default. If you will use type keyword instead interface keyword, this code will compile:

type Indexed = { [K: string]: unknown }

type NotIndexed= {
  name: string;
}

const foo = <T extends Indexed>(obj: T) => obj

const obj: NotIndexed= {
  name: 'name',
}

foo(obj) // ok

This is the difference between interfaces and types. See related answer.

So, if you use type keyword instead of interface in Example interface, it should compile

  • 1
    Thanks. I believe types are indexed by default as they are configured as 'object' and not 'structure. – Ron Rofe Jul 16 '21 at 20:43
-1
  1. Oh yes there is:
const pickFromObj = <O, K extends keyof O>(object: O, keys: K[]) => { };
  1. The code above also gets rid of your indexing error :)

Link to TS Playground here

Ján Jakub Naništa
  • 1,880
  • 11
  • 12
-2

come if you wanna see the light. do not complicate yourself.

basically


interface Person {
    /** the first name of the person */
    first: string;
    /** the first name of the person */
    last: string;
    /** contact email of the person */
    email: string;
    /** person's age */
    age:number;
}

/**
 * @description Get the value of a property from a given obj.
 *
 * @date 06/07/2021
 * @export
 * @template Obj type definition of the obj
 * @param {keyof Obj} key a property inside of our obj.
 * @param {Obj} obj any object from where we want the value.
 * @returns {Obj[key]} the value of the prop.
 * @example
 * const last = getFromObj<Person>("last", eneto);
 */
export function getFromObj<Obj>(key: keyof Obj, obj:Obj):Obj[keyof Obj] {
    return obj[key];
}

const eneto: Person ={

    first:"Ernesto",

    last:"Jara",

    email:"email@email.com",
    age:19,
}

const name = getFromObj<Person>("first", eneto);
const age = getFromObj<Person>("age", eneto);

console.log("name", name);
console.log("age", age);

There you have it, if you hover over name or age vscode or yow favorite editor will say

const name: string | number;

but you know name is an string and age is a number, so you need to let TS to know that as well.

Do it like this



const name = getFromObj<Person>("first", eneto) as string;
const age = getFromObj<Person>("age", eneto) as number;


now if you hover over name it'll show you

const name: string;
Ernesto
  • 3,944
  • 1
  • 14
  • 29