是否可以从常量对象中提取类型列表?

问题描述 投票:0回答:1

我正在尝试从数据文件中读取数据,其中所有内容都存储在数组中,并将其传输到等效的 Javascript 对象。

我希望能够使用打字稿创建一个系统,该系统自动读取数组,并将它们存储在相应的对象中,但也能够验证类型是否正确,以避免难以调试的错误。

我希望能够做的是类似以下的事情


//Template that will specify types of Typescript, but also allow me to use the keys with javascript
const SaveDataTemplate = {
    id: Number(),
    name: String(),
    someBool: Boolean(),
}

//Type to allow for type-checking the save data input/output
type tSaveData = GetKeyTypesFrom<SaveDataTemplate>; //Theoretically would return constant array type of [number, string, boolean];

//Takes in array from save data and returns key/value object
function fromSaveData(data: tSaveData): typeof SaveDataTemplate {
    const returnObj = {};
    let idx = 0;
    
    for (const key in SaveDataTemplate){
        returnObj[key] = data[idx++];
    }
    
    return returnObj as any;
}

//Takes in object which contains keys which are in SaveDataTemplate and returns array in specific format
function toSaveData(data: typeof SaveDataTemplate): tSaveData {
    const returnArr = [];
    
    for (const key in data){
        returnArr.push(data[key]);
    }
    
    return returnArr as any;
}

在像 C++ 这样的语言中,

Struct
s 会让这变得容易,但由于 Jvascript 没有这些,所以很难找到解决方法。

javascript typescript
1个回答
0
投票

无论好坏,TypeScript 都不会跟踪对象类型中键的“顺序”。此请求已被多次询问并拒绝,例如,请参阅 microsoft/TypeScript#39701。 事实上,这甚至是作为愚人节功能公告在 microsoft/TypeScript#58019 中引入的。 keyof 类型运算符

 生成键类型的 
union;同样,您无法将联合的“顺序”作为元组获得。有多种方法可以强制 TypeScript 泄露其联合的内部顺序表示,但这些方法并不能保证是您所期望的。请参阅如何将联合类型转换为元组类型。 这不是 TypeScript 的功能,而且几乎肯定永远不会。 一般来说,您确实不希望关键顺序很重要,否则

{a: string, b: number}

{b: number, a: string}
将是不同的类型,并且在绝大多数情况下,您不想跟踪
when
属性添加到对象,或对象文字中键出现的位置。 因此 TypeScript 不知道如何获取

SaveDataTemplate

并生成

[number, string, boolean]
[boolean, number, string]
或任何其他排列。同样,它也无法确定
for...in 循环
 
for (const key in SaveDataTemplate)
会做什么。 任何依赖键排序的方法都需要显式断言来告诉 TypeScript 期望什么,而这是脆弱的。例如,您的
toSaveData()
函数会执行不可预测的操作,因为 TypeScript 无法注意到您传入了
{id:0, name:"", someBool:true}
而不是
{someBool:true, id:0, name:""}
。我想说你应该完全避免这种方法。

相反,您可以考虑将数据结构更改为固有有序类型。例如,键值对的

const断言

数组文字
const saveDataTemplate =
  [["id", Number()], ["name", String()], ["someBool", Boolean()]] as const;

type SaveDataTemplate = typeof saveDataTemplate
/* type SaveDataTemplate = readonly [
     readonly ["id", number], 
     readonly ["name", string], 
     readonly ["someBool", boolean]
] */

这足以根据需要构造值类型的
tuple

type GetValueTypesFrom<T extends readonly (readonly [PropertyKey, any])[]> = { [I in keyof T]: T[I][1] } type TSaveDataValues = GetValueTypesFrom<SaveDataTemplate>; // type TSaveDataValues = readonly [number, string, boolean]

以及原始数据结构的相应对象类型(如果在键值对数组上使用 

Object.fromEntries()

,你会得到什么:
type GetObjectTypeFrom<T extends readonly (readonly [PropertyKey, any])[]> = { [I in `${number}` & keyof T as T[I][0]]: T[I][1] } type TSaveDataObject = GetObjectTypeFrom<SaveDataTemplate>; /* type TSaveDataObject = { id: number; name: string; someBool: boolean; } */

(请注意,我在类型名称中将“key”一词更改为“value”,因为 
[number, string, boolean]

是属性

values
的元组,而不是 keys。) 您可以使用键值对数组来实现您的函数,无论对象键的顺序如何,都可以保证正常工作:

function fromSaveData(data: TSaveDataValues): TSaveDataObject { const returnObj: any = {}; let idx = 0; for (const [key] of saveDataTemplate) { returnObj[key] = data[idx++]; } return returnObj; } function toSaveData(data: TSaveDataObject): TSaveDataValues { const returnArr: any = []; for (const [key] of saveDataTemplate) { returnArr.push(data[key]) } return returnArr; }

在这两种情况下,我们都会循环遍历 
saveDataTemplate

数组中的“关键”元素。无论如何,这些将始终保持相同的顺序。具体来说,在

toSaveData()
中,输入
data
对象不需要具有任何特定顺序的键。只要它是有效的
TSaveDataObject
,该函数就会一致地序列化对象:
const obj = fromSaveData([1, "two", true]);
console.log(obj);
/* {
  "id": 1,
  "name": "two",
  "someBool": true
}  */

const keys = toSaveData(obj);
console.log(keys);
/* [1, "two", true] */

const otherKeys = toSaveData({someBool: true, id: 1, name: "two"});
console.log(otherKeys);
/* [1, "two", true] */

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.