Home > Software engineering >  Typescript, index one interface keys using another interface
Typescript, index one interface keys using another interface

Time:01-11

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

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

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

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]
}

CodePudding user response:

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

CodePudding user response:

This answer is completely generic and it works.

I just want to share something similar which I did to convert Date to a custom DateTimeFormatParts formatted date using Intl, except it's partially generic.

It worked like this:

// Accepts T which has any or all Intl.DateTimeFormatOptions properties
export type DateFormatPartsRecord<T extends Intl.DateTimeFormatOptions = Intl.DateTimeFormatOptions> = {
  // modify keys to match props with result props
  [P in keyof T as P extends "fractionalSecondDigits"
    ? "fractionalSecond"
    : P extends "hour12"
    ? "dayPeriod"
    : P]?: string;
};

// argument type for my caller function
export type DateIntlOptions<T extends Intl.DateTimeFormatOptions = Intl.DateTimeFormatOptions> = {
  locale?: string | string[];
  options?: T;
};

// the function which converts formatted intl array parts to my custom object type.
export function ToDateFormatParts<T extends Intl.DateTimeFormatOptions = Intl.DateTimeFormatOptions>(
  val: number | string | Date,
  intl?: DateIntlOptions<T>
): DateFormatPartsRecord<T> {
  const toFormat = new Date(val);
  const reduced: DateFormatPartsRecord<T> = {};
  Intl.DateTimeFormat(intl?.locale, intl?.options ?? { day: "2-digit", month: "short", year: "numeric" })
    .formatToParts(toFormat)
    .forEach(({ type, value }) => {
      if (type === "literal") return;
      reduced[type] = value;
    });
  return reduced;
}

Applying this to your case, you can create a custom type based on the original:

// instead of obj2 interface:
type Obj2FromObj1<T extends Obj1> = {
  [K extends keyof T]: boolean;
} // or a shorter version: Record<keyof T, boolean>

So now you can create a function to return only what props defined in the obj1:

function Obj2ValuesFromObj1<T extends Obj1>(args: T): Obj2FromObj1<T> {
  // your handling here.
}
  • Related