是否有一种方法可以更严格地键入以下两个函数toCsv()
和toArray()
,使得typeof csv
为
[["key", "life", "goodbye"], [string, number, boolean]]
代替
[("key" | "life" | "goodbye")[], ...(string | number | boolean)[]]
并且typeof original
与typeof values
相同,而不是
{ key: any, life: any, goodbye: any }[]
我意识到不能保证使用{ key: 'value', life: 42, goodbye: false }
进行for...in
的迭代顺序,对此我很好。即使TypeScript编译器不会产生与运行时相同的顺序,也可以接受将键与每一行的相应值对齐的任何一致顺序,因为用法不依赖于任何特定顺序。
type Key<T> = Extract<keyof T, string>;
type Column<T> = [Key<T>, ...T[Key<T>][]];
type Columns<T> = [Key<T>[], ...T[Key<T>][][]];
function toCsv<T> (array: T[]): Columns<T> {
const columns: Column<T>[] = [];
for (const key in array[0]) {
columns.push([key, ...array.map(value => value[key])]);
}
const keys: Key<T>[] = [];
const rows: T[Key<T>][][] = array.map(() => []);
for (const [key, ...values] of columns) {
keys.push(key);
for (const [index, row] of rows.entries()) {
row.push(values[index]);
}
}
return [keys, ...rows];
}
function toArray<T> (csv: Columns<T>): T[] {
const [keys, ...rows] = csv;
return rows.map(
row => keys.reduce(
(o, key, index) => Object.assign(o, { [key]: row[index] }),
{} as Partial<T>
) as T
);
}
const values = [{ key: 'value', life: 42, goodbye: false }];
const csv = toCsv(values);
const original = toArray(csv);
我的解决方案有点小巧,但是可以用。
type CSV<T> = {keys: (keyof T)[], values: (T[keyof T])[][], original: T}
const toCsv = <T extends object>(values: T[]): CSV<T> => {
if(values.length === 0) {
throw new Error('Values must have length of more than one')
}
else {
return {
keys: Object.keys(values[0]) as (keyof T)[],
values: values.map(value => Object.keys(value).map(key => value[key])) as T[keyof T][][],
original: values[0]
}
}
}
const toArray = <T extends object>(csv: CSV<T>): T[] => {
return csv.values.map(values => values.reduce<T>((result, value, index) => ({...result, [csv.keys[index]]: value}), {} as T))
}
const values = [{ key: 'value', life: 42, goodbye: false }];
const csv = toCsv(values);
const original = toArray(csv);
type Result = typeof original extends typeof values ? true : never
original
的类型将与values
相同。您可以通过将光标悬停在Result
上来检查自己。
除了类型检查之外,该实现还可以在运行时中运行。