TypeScript: convert tuple type to object

J. *_*ers 5 parameters constructor tuples object typescript

Summary: I have a tuple type like this:

\n
[session: SessionAgent, streamID: string, isScreenShare: boolean, connectionID: string, videoProducerOptions: ProducerOptions | null, connection: AbstractConnectionAgent, appData: string]\n
Run Code Online (Sandbox Code Playgroud)\n

我想将其转换为这样的对象类型:

\n
type StreamAgentParameters = {\n  session: SessionAgent\n  streamID: string\n  isScreenShare: boolean\n  connectionID: string\n  videoProducerOptions: ProducerOptions | null\n  connection: AbstractConnectionAgent\n  appData: string\n}\n
Run Code Online (Sandbox Code Playgroud)\n

有没有办法做到这一点?

\n
\n

我想创建一个工厂函数来测试类以简化设置。

\n
export type Factory<Shape> = (state?: Partial<Shape>) => Shape\n
Run Code Online (Sandbox Code Playgroud)\n

我想避免手动输入类的参数,因此我寻找获取构造函数参数的可能性。还有你知道什么吗,还有ConstructorParameters助手类型。不幸的是,它返回一个元组而不是一个对象。

\n

因此,以下内容不起作用,因为元组不是对象。

\n
type MyClassParameters = ConstructorParameters<typeof MyClass>\n// \xe2\x86\xb5 [session: SessionAgent, streamID: string, isScreenShare: boolean, connectionID: string, videoProducerOptions: ProducerOptions | null, connection: AbstractConnectionAgent, appData: string]\n\nconst createMyClassParameters: Factory<MyClassParameters> = ({\n  session = new SessionAgent(randomRealisticSessionID()),\n  streamID = randomRealisticStreamID(),\n  isScreenShare = false,\n  connectionID = randomRealisticConnectionID(),\n  videoProducerOptions = createPopulatedProducerOptions(),\n  connection = new ConnectionAgent(\n    new MockWebSocketConnection(),\n    \'IP\',\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ),\n  appData = \'test\',\n} = {}) => ({\n  session,\n  streamID,\n  isScreenShare,\n  connectionID,\n  videoProducerOptions,\n  connection,\n  appData,\n})\n
Run Code Online (Sandbox Code Playgroud)\n

我尝试创建一个将元组转换为对象的辅助类型,但我最好的尝试是这样(但它不起作用)。

\n
type TupleToObject<T extends any[]> = {\n  [key in T[0]]: Extract<T, [key, any]>[1]\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我怎么解决这个问题?

\n

cap*_*ian 5

为了将任何元组转换为对象,您可以使用此实用程序类型:

\n
type Reducer<\n  Arr extends Array<unknown>,\n  Result extends Record<number, unknown> = {},\n  Index extends number[] = []\n  > =\n  Arr extends []\n  ? Result\n  : Arr extends [infer Head, ...infer Tail]\n  ? Reducer<[...Tail], Result & Record<Index[\'length\'], Head>, [...Index, 1]>\n  : Readonly<Result>;\n\n// Record<0, "hi"> & Record<1, "hello"> & Record<2, "\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd1\x96\xd1\x82">\ntype Result = Reducer<[\'hi\', \'hello\', \'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd1\x96\xd1\x82\']>;\n
Run Code Online (Sandbox Code Playgroud)\n

由于我们是从元组进行转换,因此您只能使用元素索引作为键。

\n

为了保留有关键/索引的信息,我Index在类型实用程序中添加了额外的泛型类型。每次迭代我都会添加并计算I1的新长度index

\n

不允许您使用元组标签作为键,因为:

\n
\n

它们\xe2\x80\x99 纯粹用于文档和工具。

\n
\n


jca*_*alz 5

正如其他答案中提到的,无法将元组标签转换为字符串文字类型;这些标签仅用于文档,不会影响类型系统:类型[foo: string][bar: string]以及[string]都彼此等效。[foo: string]所以任何要变成的方法{foo: string}也应该[bar: string]变成{foo: string}。所以我们需要放弃捕获元组标签。


元组的真正键是数字字符串,例如"0"1"。如果您只想将元组转换为类似的类型,仅使用那些类似数字的键,而不是所有数组属性和方法,您可以这样做:

type TupleToObject<T extends any[]> = Omit<T, keyof any[]>
Run Code Online (Sandbox Code Playgroud)

这只是使用实用程序Omit<T, K>类型来忽略所有数组中存在的任何元组属性(例如lengthpush等)。这也或多或少相当于

type TupleToObject<T extends any[]> = 
  { [K in keyof T as Exclude<K, keyof any[]>]: T[K] }
Run Code Online (Sandbox Code Playgroud)

它使用显式过滤掉键的映射类型

这是它在元组类型上的行为方式:

type StreamAgentObjectWithNumericlikeKeys = TupleToObject<StreamAgentParameters>
/* type StreamAgentObjectWithNumericlikeKeys = {
    0: SessionAgent;
    1: string;
    2: boolean;
    3: string;
    4: ProducerOptions | null;
    5: AbstractConnectionAgent;
    6: string;
} */
Run Code Online (Sandbox Code Playgroud)

您还可以创建一个函数对实际值执行相同的操作:

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};
Run Code Online (Sandbox Code Playgroud)

如果除了类型元组之外,您还愿意保留属性名称元组,则可以编写一个将数字元组键映射到相应名称的函数:

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
}
*/
Run Code Online (Sandbox Code Playgroud)

您可以创建一个函数对实际值执行相同的操作:

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}
Run Code Online (Sandbox Code Playgroud)

Playground 代码链接