Typing an array of generic inferred types

情到浓时终转凉″ 提交于 2021-02-04 18:52:06

问题


I'm trying to create the type of an array of objects. The first and second key of this object is required to match. For example:

[{ 
  key1: "hi",
  key2: "world"
},{
  key1: 1,
  key2: 2
},{
  key1: true,
  key2: false
}]

This is what I've come up with but it doesn't exactly work. I have a generic type to define the object in the array. When calling it to generate the array type, an error is raised.

type ArrayItem<T> = {
  key1: T,
  key2: T
}

// This raises an error Generic Type ArrayItem requires 1 type argument
type Array = ArrayItem<T>[]

What is the best way to type a nested object like this (with type inference support)?


回答1:


If you don't have a finite list of possible types for T in ArrayItem<T>, there's no concrete type in TypeScript corresponding to Array<ArrayItem<T>>. To represent such a thing as a non-generic type would require something like existential types, which TypeScript doesn't directly support.

(If you do have a finite list, like ArrayItem<string> | ArrayItem<number> | ArrayItem<boolean>, then you can just use a union like in the other answer.)

The closest you can come to this in TypeScript is as a generic type, and the best you'll do in terms of inference and compiler warnings will be to represent this as something like a generic constraint.

One way to do this is to write a generic helper function asMyArray() accepting a tuple, and the compiler will check each element of the tuple to make sure it meets the constraint. One snag is that {key1: "hi", key2: 2} does meet the constraint if you allow things like string | number as T. To prevent the compiler from happily accepting all pairs of types, I will try to make it infer T from key1 only (see microsoft/TypeScript#14829 to see ways to prevent inferring from a particular inference site), and then just check that key2 matches that:

type NoInfer<T> = [T][T extends any ? 0 : 1]

const asMyArray = <T extends readonly any[]>(
    x: [...({ [K in keyof T]: { key1: T[K], key2: NoInfer<T[K]> } })]) =>
    x;

The generic type parameter T is a tuple corresponding to the key1 values for each element of the passed-in array. The passed-in array, x, is of a mapped tuple type. The & {} bit lowers the inference priority of key2. The [... ] bit just prompts the compiler to infer a tuple and not an array (where it wouldn't be able to tell the different elements apart), Lets test it out:

const myArray = asMyArray([{
    key1: "hi",
    key2: "world"
}, {
    key1: 1,
    key2: 2
}, {
    key1: true,
    key2: false
}])
// const asMyArray: <[string, number, boolean]>(...)

You can see that T is inferred as [string, number, boolean]. This succeeds, while the following, in which T is inferred the same way, fails:

const badArray = asMyArray([{
    key1: "hi", key2: 123 // error!
    // -------> ~~~~
    // number not assignable to string
}, {
    key1: 1, key2: "world" // error!
    // ----> ~~~~
    // string not assignable to number
}, {
    key1: true, key2: false
}]);

Looks like what you want. Okay, hope that helps; good luck!

Playground link to code




回答2:


Dealing with arrays can be messy, even with generic typing. It depends deeply on how your items will be used after the array is initialized. Based on your snippets, I would start creating the interfaces for each "kind" of entry in the array, to get a strong typing for each distinct set of properties.

export interface FooItemType {
  key1: string,
  key2: string,
}


export interface BarItemType {
  key1: boolean,
  key2: boolean,
}

Then, you may create a new type that maps the interfaces you've defined before.

export type ItemType = BarItemType | FooItemType;

After that, you may declare it as simple ItemType array.

export myArr: ItemType[] = [{ 
  key1: "hi",
  key2: "world"
},{
  key1: 1,
  key2: 2
},{
  key1: true,
  key2: false
}]

Although this approach is strong typed, it may result in some manual casting after you get the myArray[i] item from the array. Sometimes we design the "typing" before thinking about its usage in the application, so the data structure has to be designed looking for the it as a whole.



来源:https://stackoverflow.com/questions/62487613/typing-an-array-of-generic-inferred-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!