【问题标题】:TypeScript: convert tuple type to objectTypeScript:将元组类型转换为对象
【发布时间】:2021-11-04 04:34:13
【问题描述】:

总结:我有一个这样的元组类型:

[session: SessionAgent, streamID: string, isScreenShare: boolean, connectionID: string, videoProducerOptions: ProducerOptions | null, connection: AbstractConnectionAgent, appData: string]

我想把它转换成这样的对象类型:

type StreamAgentParameters = {
  session: SessionAgent
  streamID: string
  isScreenShare: boolean
  connectionID: string
  videoProducerOptions: ProducerOptions | null
  connection: AbstractConnectionAgent
  appData: string
}

有没有办法做到这一点?


我想为一个类的测试创建一个factory function 以简化设置。

export type Factory<Shape> = (state?: Partial<Shape>) => Shape

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

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

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,
})

我尝试创建一个将元组转换为对象的辅助类型,但我最好的尝试是这样(但没有成功)。

type TupleToObject<T extends any[]> = {
  [key in T[0]]: Extract<T, [key, any]>[1]
}

我该如何解决这个问题?

【问题讨论】:

    标签: typescript object parameters constructor tuples


    【解决方案1】:

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


    元组的真正键是数字字符串,如"0"1"。如果你只是想把一个元组变成一个类似的类型,只用那些类似数字的键而不是所有的数组属性和方法,你可以这样做:

    type TupleToObject<T extends any[]> = Omit<T, keyof any[]>
    

    这只是使用the Omit&lt;T, K&gt; utility type 来忽略所有数组中存在的任何元组属性(如lengthpush 等)。这也或多或少等同于

    type TupleToObject<T extends any[]> = 
      { [K in keyof T as Exclude<K, keyof any[]>]: T[K] }
    

    明确使用a mapped type with filtered out keys

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

    type StreamAgentObjectWithNumericlikeKeys = TupleToObject<StreamAgentParameters>
    /* type StreamAgentObjectWithNumericlikeKeys = {
        0: SessionAgent;
        1: string;
        2: boolean;
        3: string;
        4: ProducerOptions | null;
        5: AbstractConnectionAgent;
        6: string;
    } */
    

    你也可以创建一个函数来对实际值做同样的事情:

    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};
    

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

    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
    }
    */
    

    你可以创建一个函数来对实际值做同样的事情:

    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}
    

    Playground link to code

    【讨论】:

    • 聪明的解决方案,一如既往!
    【解决方案2】:

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

    type Reducer<
      Arr extends Array<unknown>,
      Result extends Record<number, unknown> = {},
      Index extends number[] = []
      > =
      Arr extends []
      ? Result
      : Arr extends [infer Head, ...infer Tail]
      ? Reducer<[...Tail], Result & Record<Index['length'], Head>, [...Index, 1]>
      : Readonly<Result>;
    
    // Record<0, "hi"> & Record<1, "hello"> & Record<2, "привіт">
    type Result = Reducer<['hi', 'hello', 'привіт']>;
    

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

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

    您不能使用tuple labels 作为密钥,因为:

    它们纯粹是为了文档和工具。

    【讨论】:

    • 这个解决方案比this one:type TupleToObject&lt;T extends any[]&gt; = Omit&lt;T, keyof any[]&gt;有什么好处吗?
    • @jcalz 我确实认为您的解决方案更好。
    • 那么你是想让我发布一个单独的答案还是要编辑这个?
    • @jcalz 请作为单独的答案发布
    • 好的,here。又像往常一样长篇大论了。
    【解决方案3】:

    TL;DR:不可能将元组类型转换为对象,因为元组中缺少有关键的信息。

    当你说你有一个像[session: SessionAgent, streamID: string] 这样的元组类型时,我猜你的意思是[SessionAgent, string]

    您不能将变量名保留在元组旁边,它们会被丢弃,并且无法恢复丢失的信息。

    如果适合您,一种解决方法是将 MyClass 构造函数签名从位置参数转换为命名参数。

    // from:
    class MyClass {
      constructor(session: SessionAgent, streamID: string) {…}
    }
    
    // to:
    class MyClass {
      constructor(opt: { session: SessionAgent, streamID: string }) {…}
    }
    
    // now you can infer:
    type MyClassParameters = ConstructorParameters<typeof MyClass>[0]
    // ↵ { session: SessionAgent, streamID: string }
    

    【讨论】:

      猜你喜欢
      • 2019-11-21
      • 1970-01-01
      • 2020-12-28
      • 2020-04-11
      • 1970-01-01
      • 2018-03-26
      • 2022-09-27
      • 1970-01-01
      • 2023-04-01
      相关资源
      最近更新 更多