Rush
Rush

Reputation: 189

TypeScript interface where object keys are values of another object

This is my interface:

interface MyInterface {
    a: string
    b: string
}

I have objectA from this interface:

const objectA: MyInterface = { 
    a: val1, 
    b: val2
}

I then have a function that reads an API response and creates a mapping as follows:

const createMapping = (response) => {
  const ret = {};
  response.forEach(item => {
    ret[objectA[item]] = true;
  })
 return ret;
}

Is there a way I can create an interface for the return value of createMapping such that the interface's keys are the value of MyIterface?

A return value could be {val1: true}, {val2: true}, or {val1: true, val2:true}

Upvotes: 3

Views: 1626

Answers (3)

jcalz
jcalz

Reputation: 330086

I'm not really sure where objectA comes from, but you can get what you want. First, wherever objectA comes from, you should let TypeScript know that the values are particular string literal types. There are different ways to do this. The most straightforward (but not DRY) way is to use type assertions:

interface MyInterface {
  a: string
  b: string
}

const objectA = {
  a: "val1" as "val1",
  b: "val2" as "val2"
} 

Note that objectA is not being annotated as a MyInterface, since you don't want TypeScript to forget that its properties are "val1" and "val2". Its compatibility with MyInterface will be verified later.

Now we can make a function which takes anything MyInterface-like (with string properties) and produces a createMapping() function which uses it:

const makeCreateMapping = <O extends MyInterface & {[k: string]: string}>(o: O) =>
  (response: (keyof O)[]) => {
    const ret = {} as Partial<Record<O[keyof O], true>>;
    response.forEach(item => {
      ret[o[item]] = true;
    })
    return ret;
  }

The O parameter is the type of your MyInterface object. Let's call makeCreateMapping() with objectA:

const createMapping = makeCreateMapping(objectA);

That's where the fact that objectA is a MyInterface comes in. If it hadn't been, the compiler would have yelled at you. Now if you inspect the type of createMapping, it is:

const createMapping: (response: ("a" | "b")[]) => Partial<Record<"val1" | "val2", true>>

That is, a function which takes an array of "a" or "b", and returns a Partial<Record<"val1" | "val2", true>> which is essentially {val1?: true, val2?: true} whose valid values include {val1: true}, {val2: true}, and {val1: true, val2: true}.

To demonstrate:

declare const response: (keyof typeof objectA)[]
const mapping = createMapping(response);
mapping.val1 // true | undefined
mapping.val2 // true | undefined
mapping.val3 // error, doesn't exist

Hope that helps. Good luck!

Upvotes: 2

Behrooz
Behrooz

Reputation: 2371

Yes, you can specify a return type or interface for the arrow function.

const createMapping = (response): MyInterface => {
  const ret = {};
  response.forEach(item => {
    ret[objectA[item]] = true;
  })
return ret;
}

In this case, when you execute the function createMapping, the output (ret) is expected to be of MyInterface type.

Upvotes: 0

Aron
Aron

Reputation: 9258

You would only get the API response values at runtime, at which point your TypeScript code has already been compiled away into JavaScript, so the answer is: no you would not be able to do this.

Upvotes: 1

Related Questions