3

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}

1
  • 2
    Have a look at how advanced types (specifically with keyof) work, and note that the type-checker runs at compile time, not run-time. As Aaron stated in his answer, you can't, so you'd need to run your own validation at run-time. Commented Mar 5, 2018 at 18:21

3 Answers 3

2

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!

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot! "Record" really did the trick and I got it working as I wanted to.
1

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.

1 Comment

Thanks! I thought this was the case but just wanted to be sure.
0

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.

1 Comment

Yeah I have tried this earlier but it doesn't work. I can always write up another interface but was curious if there was another optimal way to handle this. Thanks though!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.