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!