Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript, index one interface keys using another interface

Tags:

typescript

I have two interfaces with identical optional keys, but different values:

interface Obj1 {
  a?: string
  b?: string
  c?: number 
}

interface Obj2 {
  a: boolean
  b: string
  c: number 
}

Obj1 is served as a function argument, the other, Obj2, is the return of that function. I want the return type to identify only the given keys on Obj1. So if Obj1 contained only a and b then Obj2 will contain only a and b as well.

I tried with the approach bellow, but I get a ts error Type 'Property' cannot be used to index type 'ValueType'

type Obj1KeysWithObj2Values<KeyType extends Obj1, ValueType extends Obj2> = {
  [Property in keyof KeyType]: ValueType[Property]
}

UPDATE: The function and its call

const myFunc = <T extends Obj1>({ a, b, c }: T) => {
  const returnObj: Partial<Obj2> = {}
  if (a) {
    returnObj.a = true
  }
  if (b) {
    returnObj.b = '1'
  }
  if (c) {
    returnObj.c = 20
  }
  return returnObj as Obj1KeysWithObj2Values<T, Obj2>
}

const resultObj = myFunc({ a: 'hello', b: 'hello' })

If you try it, then you see on resultObj you get what ever you pass to the function, as long as it is in interface Obj1, regardless of Obj2.

like image 361
U Rogel Avatar asked Feb 24 '26 01:02

U Rogel


2 Answers

Consider using Pick<Type, Keys> which is a part of standard library:

interface Obj1 {
 a?: string
 b?: string
 c?: string
}

interface Obj2 {
 a?: boolean
 b?: boolean
 c?: boolean
}

type Result = Pick<Obj2, keyof Obj1>

First argument represents source object, second argument represents a union of keys which should be picked

In your case, you also need to make an intersection of Obj1 keys and Obj2 keys:

interface Obj1 {
  a?: string
  b?: string
  c?: number
}

interface Obj2 {
  a: boolean
  b: string
  c: number
}

const myFunc = <T extends Obj1>({ a, b, c }: T) => {
  const returnObj: Partial<Obj2> = {}
  if (a) {
    returnObj.a = true
  }
  if (b) {
    returnObj.b = '1'
  }
  if (c) {
    returnObj.c = 20
  }
  return returnObj as Pick<Obj2, keyof T & keyof Obj2>
}

const resultObj = myFunc({ a: 'hello', b: 'hello' })

Playground

like image 187
captain-yossarian Avatar answered Feb 26 '26 21:02

captain-yossarian


The issue is that although you know that Obj2 will always have a superset of Obj1's keys, TypeScript doesn't know that, so it needs reassuring. :-) If you want to use those generics, you can clear that error by using a conditional type to test that the property key is also present in ValueType:

type Obj1KeysWithObj2Values<KeyType extends Obj1, ValueType extends Obj2> = {
    [Property in keyof KeyType]: Property extends keyof ValueType ? ValueType[Property] : never;
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
};

Playground example

like image 34
T.J. Crowder Avatar answered Feb 26 '26 22:02

T.J. Crowder