我正在尝试从数据文件中读取数据,其中所有内容都存储在数组中,并将其传输到等效的 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 没有这些,所以很难找到解决方法。
无论好坏,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]
以及原始数据结构的相应对象类型(如果在键值对数组上使用
,你会得到什么:
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 代码链接