我编写了一个函数,它以一个结构(类型为Struct)作为输入,该结构可以包含原始类型以及Map和Set对象,并将其转换为可以被JSON序列化的结构。投入实例:
let a = 'hello'; // -> "hello"
let b = new Map<string, Set<number>>([['a', new Set<number>([1, 3])]]); // -> {"a": [1, 3]}
let c = {a: new Set<number[]>([[1, 2]])}; // -> {"a": [[1, 2]]}
let d = [{e: false}]; // -> [{"e": false}]但是,我发现我的代码特别冗长,我真的不确定它的安全性:
type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
type Struct = Json | Struct[] | { [key: string]: Struct } | Map<string, Struct> | Set<Struct>;
function isJson(test: any): test is Json {
if (test == null || ['string', 'number', 'boolean'].indexOf(typeof test) != -1)
return true;
if (Array.isArray(test)) {
// if at least one of the values is not JSON serializable, the array is not JSON-serializable
for (let value of test)
if (!isJson(value))
return false;
return true;
}
if (typeof test == 'object') {
// if it is not a plain object, the object is not JSON-serializable
if (Object.getPrototypeOf(test) != null && test.constructor != Object)
return false;
// if there are symbol properties, the object is not JSON-serializable
if (Object.getOwnPropertySymbols(test).length > 0)
return false;
// if at least one of the values is not JSON serializable, the object is not JSON-serializable
for (let [key, value] of Object.entries(test))
if (!isJson(test[key]))
return false;
return true;
}
return false;
}
function toJson(struct: Struct) {
let json: Json = null;
if (isJson(struct))
json = struct;
else if (Array.isArray(struct) || struct instanceof Set) {
json = [];
let structCast = struct instanceof Set ? struct as Set<Struct> : struct as Struct[];
for (let value of structCast)
json.push(toJson(value));
}
else if (Object.getPrototypeOf(struct) == null || struct.constructor == Object || struct instanceof Map) {
json = {};
let structCast = struct instanceof Map ? struct as Map<string, Struct> : Object.entries(struct);
for (let [key, value] of structCast)
json[key] = toJson(value);
}
return json;
}我对isJson函数特别恼火。没有办法摆脱它吗?我知道在TypeScript编译之后,大多数的排版功能都会丢失,但是有没有更好的方法来做我想做的事情呢?
谢谢你的帮助。
发布于 2021-03-17 14:08:09
我将假设您正在编写TypeScript代码,而toJson()的用户也在编写TypeScript代码。如果这不是真的,而且如果toJson()函数是由某个纯JavaScript代码调用的,那么您将失去任何编译时间保证,并且需要根据您的需要向toJson()添加尽可能多的运行时检查。从现在开始,我假设所有相关代码都将通过编译步骤。
在这种情况下,您可能不再担心在isJson()中处理的一些奇怪的边缘情况。如果TypeScript Struct类型定义已经禁止某些内容,那么您不需要编写运行时代码来处理它。另一方面,Struct允许但不允许的任何内容都需要运行时检查。例如,在TypeScript中通常不可能说“拒绝具有任何symbol-valued键的对象类型”。您可以使用仿制药使编译器拒绝任何已知具有此类键的对象类型,但这要复杂得多,而且通常情况下,像{a: string}这样的类型的对象可能也可能没有其他未知键。我建议,如果您确实需要这样的检查,可以使用throw语句进行检查,因为您无论如何都无法在运行时之前捕获它们(除非您希望toJson()返回Json | undefined或其他一些失败标记)。
因此,让我们通过将我们需要的isJson()检查合并到toJson()函数并消除isJson()来解决这个问题。这里有一个可能的toJson()实现
function toJson(struct: Struct): Json {
if (struct === null || typeof struct === "string" ||
typeof struct === "number" || typeof struct === "boolean") {
return struct;
}
if (Array.isArray(struct)) {
return struct.map(toJson);
}
if (struct instanceof Set) {
return Array.from(struct).map(toJson);
}
if (struct instanceof Map) {
return Object.fromEntries(
Array.from(struct).map(([k, v]) => [k, toJson(v)])
);
}
return Object.fromEntries(
Object.entries(struct).map(([k, v]) => [k, toJson(v)])
);
}开始时的四个基本检查可以重构为array.includes()或类似的代码,但是TypeScript编译器将不了解发生了什么,您将需要return struct as Json。我得到它的方式更冗长,但编译器100%确定struct是该块中的有效Json。两种方法都行。
其余的检查只是使用内置的JavaScript函数和方法,如Array.from()、Array.prototype.map()、Object.entries()和Object.fromEntries()。
至于类型,如果需要,您可以给toJson()一个调用签名,它试图将输入类型实际映射到输出类型。例如:
type ToJson<T> =
Struct extends T ? Json :
Json extends T ? T :
T extends Map<infer K, infer S> ? { [P in Extract<K, string>]: ToJson<S> } :
T extends Set<infer S> ? ToJson<S>[] :
T extends object ? { [K in keyof T]: ToJson<T[K]> } :
T;
function toJson<T extends Struct>(struct: T): ToJson<T>;ToJson<T>类似于实现,但表示为类型操作。如果输入类型T只是Struct,那么输出Json。如果它已经是Json的一个子类型,那么输出T。如果是Map<K, S>,那么生成一个对象,其键为K,其值为ToJson<S>。如果是Set<S>,则输出元素为ToJson<S>的数组。如果它是一个对象(包括数组),那么通过ToJson映射所有条目。最后,如果它只是一个基元类型,则返回原语类型。
让我们看看这一切是否有效:
const foo = {
str: "hello",
num: 123,
boo: true,
nul: null,
jArr: ["one", 2, { x: 3 }],
jObj: { a: "one", b: 2, c: { x: 3 } },
set: new Set([1, 2, 3]),
map: new Map([["a", 1], ["b", 2]])
}
const json = toJson(foo);编译器看到的类型如下:
/* const json: {
str: string;
num: number;
boo: boolean;
nul: null;
jArr: (string | number | {
x: number;
})[];
jObj: {
a: string;
b: number;
c: {
x: number;
};
};
set: number[];
map: {
[x: string]: number;
};
} */注意如何转换set和map属性。让我们确保实现也有效:
console.log(json);
/* {
"str": "hello",
"num": 123,
"boo": true,
"nul": null,
"jArr": [
"one",
2,
{
"x": 3
}
],
"jObj": {
"a": "one",
"b": 2,
"c": {
"x": 3
}
},
"set": [
1,
2,
3
],
"map": {
"a": 1,
"b": 2
}
} */也不错。编译器类型与实现值一致的事实意味着,如果您愿意,可以对其执行其他操作,并获得一些类型检查和提示:
console.log(json.set[0].toFixed(2)); // 1.00https://stackoverflow.com/questions/66673423
复制相似问题