Issue
Summary: I have a tuple type like this:
[session: SessionAgent, streamID: string, isScreenShare: boolean, connectionID: string, videoProducerOptions: ProducerOptions | null, connection: AbstractConnectionAgent, appData: string]
and I want to convert it to an object type like this:
type StreamAgentParameters = {
session: SessionAgent
streamID: string
isScreenShare: boolean
connectionID: string
videoProducerOptions: ProducerOptions | null
connection: AbstractConnectionAgent
appData: string
}
Is there a way to do that?
I want to create a factory function for tests for a class to simplify the setup.
export type Factory<Shape> = (state?: Partial<Shape>) => Shape
I want to avoid manually typing out the parameters for the class, so I looked for possibilities to get the parameters for the constructor. And what do you know, there is the ConstructorParameters
helper type. Unfortunately, it returns a tuple instead of an object.
Therefore the following doesn't work because a tuple is NOT an object.
type MyClassParameters = ConstructorParameters<typeof MyClass>
// ↵ [session: SessionAgent, streamID: string, isScreenShare: boolean, connectionID: string, videoProducerOptions: ProducerOptions | null, connection: AbstractConnectionAgent, appData: string]
const createMyClassParameters: Factory<MyClassParameters> = ({
session = new SessionAgent(randomRealisticSessionID()),
streamID = randomRealisticStreamID(),
isScreenShare = false,
connectionID = randomRealisticConnectionID(),
videoProducerOptions = createPopulatedProducerOptions(),
connection = new ConnectionAgent(
new MockWebSocketConnection(),
'IP',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
),
appData = 'test',
} = {}) => ({
session,
streamID,
isScreenShare,
connectionID,
videoProducerOptions,
connection,
appData,
})
I tried creating a helper type that converts a tuple to an object, but my best attempt was this (and it didn't work).
type TupleToObject<T extends any[]> = {
[key in T[0]]: Extract<T, [key, any]>[1]
}
How can I solve this problem?
Solution
As mentioned in the other answers, there's no way to convert tuple labels into string literal types; the labels are just for documentation and don't affect the type system: the types [foo: string]
and [bar: string]
and [string]
are all equivalent to each other. So any method to turn [foo: string]
into {foo: string}
should also turn [bar: string]
into {foo: string}
. So we need to give up on capturing tuple labels.
The real keys of a tuple are numeric strings like "0"
and 1"
. If you just want to turn a tuple into a similar type with just those numeric-like keys and not all the array properties and methods, you can do it like this:
type TupleToObject<T extends any[]> = Omit<T, keyof any[]>
This just uses the Omit<T, K>
utility type to ignore any tuple properties that exist in all arrays (like length
, push
, etc). This is also more or less equivalent to
type TupleToObject<T extends any[]> =
{ [K in keyof T as Exclude<K, keyof any[]>]: T[K] }
which uses a mapped type with filtered out keys explicitly.
Here's how it behaves on your tuple type:
type StreamAgentObjectWithNumericlikeKeys = TupleToObject<StreamAgentParameters>
/* type StreamAgentObjectWithNumericlikeKeys = {
0: SessionAgent;
1: string;
2: boolean;
3: string;
4: ProducerOptions | null;
5: AbstractConnectionAgent;
6: string;
} */
You could also make a function to do the same thing to actual values:
const tupleToObject = <T extends any[]>(
t: [...T]) => ({ ...t } as { [K in keyof T as Exclude<K, keyof any[]>]: T[K] });
const obj = tupleToObject(["a", 2, true]);
/* const obj: {
0: string;
1: number;
2: boolean;
} */
console.log(obj) // {0: "a", 1: 2, 2: true};
If you are willing to hold onto an tuple of property names in addition to your tuple of types, you can write a function which maps the numeric tuple keys to the corresponding name:
type TupleToObjectWithPropNames<
T extends any[],
N extends Record<keyof TupleToObject<T>, PropertyKey>
> =
{ [K in keyof TupleToObject<T> as N[K]]: T[K] };
type StreamAgentParameterNames = [
"session", "streamID", "isScreenShare", "connectionID",
"videoProducerOptions", "connection", "appData"
];
type StreamAgentObject =
TupleToObjectWithPropNames<StreamAgentParameters, StreamAgentParameterNames>
/*
type StreamAgentObject = {
session: SessionAgent
streamID: string
isScreenShare: boolean
connectionID: string
videoProducerOptions: ProducerOptions | null
connection: AbstractConnectionAgent
appData: string
}
*/
And you can make a function to do the same to actual values:
const tupleToObjectWithPropNames = <T extends any[],
N extends PropertyKey[] & Record<keyof TupleToObject<T>, PropertyKey>>(
tuple: [...T], names: [...N]
) => Object.fromEntries(Array.from(tuple.entries()).map(([k, v]) => [(names as any)[k], v])) as
{ [K in keyof TupleToObject<T> as N[K]]: T[K] };
const objWithPropNames = tupleToObjectWithPropNames(["a", 2, true], ["str", "num", "boo"])
/* const objWithPropNames: {
str: string;
num: number;
boo: boolean;
} */
console.log(objWithPropNames); // {str: "a", num: 2, boo: true}
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.