Reputation: 567
The return object needs to have only the keys that the argument object has and the keys have to be strings.
This is what I have so far:
type ArgumentObject<Key extends string> = Record<Key, string>;
type ResultObject<Key extends string> = Record<Key, boolean>;
function someFunction<Key extends string>(arg: ArgumentObject<Key>) {
const result: ResultObject<keyof typeof arg> = {} as ResultObject<Key>;
for (const k in Object.keys(arg)) {
result[k as Key] = true;
}
return result;
}
Typescript correctly infers the shape of the result object only if the keys of the argument object are strings. But you can also put number
and symbol
as keys in the argument object.
const result = someFunction({
a: "abc",
b: "def",
1: "number allowed as key",
[Symbol("a")]: "symbol also"
})
result.a // intellisense doesn't work
/////////////////////////////////////
const result = someFunction({
a: "abc",
b: "def"
})
result.a // intellisense works
Also, it would be great to not have to do all the type casting with as
.
Upvotes: 1
Views: 743
Reputation: 33929
First, there might be a misconception: when you provide a numeric index as a property when defining an object literal, it is coerced to a string
. So in your example, 1
is a string, not a number:
{
a: "abc",
b: "def",
1: "number allowed as key",
[Symbol("a")]: "symbol also"
}
TypeScript is structurally-typed, so as long as a type can extend
(be a subtype of) a required type, the compiler will allow it. In your case, the object passes the requirement of having string keys with string values (it also happens to have a symbol key).
One way to handle this is to use a conditional return type, where in the valid branch, it is a mapped type. So, while you can still pass in objects that have undesirable properties, you won't be able to use the return value if you do.
function someFunction <T extends Record<PropertyKey, string>>(arg: T): symbol extends keyof T ? never : number extends keyof T ? never : {
[K in keyof T]: boolean;
} {
const result = {} as any;
for (const k in Object.keys(arg)) result[k as keyof T] = true;
return result;
}
const result1 = someFunction({
a: "abc",
b: "def",
1: "number allowed as key",
[Symbol("a")]: "symbol also",
});
result1; // never
result1.a; /*
^
Property 'a' does not exist on type 'never'.(2339) */
const result2 = someFunction({
a: 42, /*
^
Type 'number' is not assignable to type 'string'.(2322) */
b: "def"
});
result2; // never
result2.a; /*
^
Property 'a' does not exist on type 'never'.(2339) */
const result3 = someFunction({
a: "abc",
b: "def"
});
result3; // { a: boolean; b: boolean; }
result3.a; // boolean
Upvotes: 1