In my opinion, the optimal way to structure this code is as follows: By utilizing KV
, which represents a generic key/value object type passed within the xs
argument of the fromArray()
function (or its related types), we can define the output object type as KeyValToObj<T>
:
type KeyValToObj<KV extends { key: PropertyKey, value: any }> =
{ [K in KV['key']]: Extract<KV, { key: K }>['value'] };
This setup ensures that when KV
is a union like
{key: "a", value: string} | {key: "b", value: number}
, the resulting
KeyValToObj<KV>
will have keys for every unique
key
property present in
KV</code, with each corresponding value being retrieved from the member of <code>KV
having a matching
key
of type
K
:
{a: string; b: number}
.
The next step involves defining the signature of the fromArray()
function:
function fromArray<XS extends Array<{ key: K, value: any }>, K extends PropertyKey>(
xs: XS | []): KeyValToObj<XS[number]> {
const obj = {} as any;
for (const x of xs) {
obj[x.key] = x.value
}
return obj
}
With an input of type XS
, the function outputs KeyValToObj<XS[number]>
, representing the object type associated with the union of element types found within XS
. Utilizing a type assertion to any
is necessary to address potential compiler errors due to challenges verifying the accurate output type.
To ensure proper type inference, it's crucial to include certain nuances within the signature. For example, the presence of the K
type offers guidance for inferring string literal types associated with the key
properties. Similarly, including | []
contributes to extracting tuple types from XS
, instead of focusing on unordered arrays. These details assist in preventing unwanted behavior during typing inferred by the compiler.
Let’s put this code into action:
const geometry = fromArray([
{ key: 'circle', value: { color: 'blue', radius: 3 } },
{ key: 'rectangle', value: { color: 'red', width: 3, height: 2 } },
{ key: 'line', value: { length: 5 } }
])
geometry.circle
// Expected: { color: string; radius: number; }
geometry.circle.radius;
geometry.line
// Expected: { length: number; }
geometry.line.length;
geometry.rectangle
// Expected: { color: string; width: number; height: number; }
geometry.rectangle.width;
The implementation seems accurate, providing the desired autocomplete functionality. Notably, in instances of a duplicate key, the output type incorporates a union of relevant value types attributed to that specific key.
I trust this explanation proves helpful to you. Best of luck with your endeavors!
Access the Playground link here