所以,我将回答我认为您的问题,如果我错了,请随意忽略我?
您的问题是您有一堆带有方法的类,但是当您将这些实例序列化为 JSON 然后将它们反序列化回来时,您最终会得到普通的 JavaScript 对象,而不是类的实例。处理这个问题的一种方法是使用一个自定义的反序列化器,它知道你的类并且可以将普通的旧 JavaScript 对象“水合”或“恢复”成真正的类实例。 JSON.parse() 函数允许您指定一个名为 reviver 的回调参数,该参数可用于执行此操作。
首先,我们需要建立一个系统,reviver 将通过该系统了解您的可序列化类。我将使用class decorator,它将每个类构造函数添加到reviver 可以使用的注册表中。我们将要求可序列化的类构造函数可以分配给我们可以调用 Serializable 的类型:它需要有一个无参数构造函数,并且它构造的东西需要有一个 className 属性:
// a Serializable class has a no-arg constructor and an instance property
// named className
type Serializable = new () => { readonly className: string }
// store a registry of Serializable classes
const registry: Record<string, Serializable> = {};
// a decorator that adds classes to the registry
function serializable<T extends Serializable>(constructor: T) {
registry[(new constructor()).className] = constructor;
return constructor;
}
现在,当您想要反序列化某些 JSON 时,您可以检查序列化的事物是否具有 className 属性,该属性是注册表中的一个键。如果是这样,您在注册表中使用该类名的构造函数,并通过Object.assign() 将属性复制到其中:
// a custom JSON parser... if the parsed value has a className property
// and is in the registry, create a new instance of the class and copy
// the properties of the value into the new instance.
const reviver = (k: string, v: any) =>
((typeof v === "object") && ("className" in v) && (v.className in registry)) ?
Object.assign(new registry[v.className](), v) : v;
// use this to deserialize JSON instead of plain JSON.parse
function deserializeJSON(json: string) {
return JSON.parse(json, reviver);
}
好的,现在我们已经有了,让我们创建一些类。 (我在这里使用您的原始定义,在您进行编辑之前。)请注意,我们需要添加一个 className 属性,并且我们必须有一个无参数构造函数(如果您不指定构造函数,这将免费发生,因为default constructor 是无参数的):
// mark each class as serializable, which requires a className and a no-arg constructor
@serializable
class StepType1 implements IStep {
id: number = 0;
name: string = "";
prop1: string = "";
readonly className = "StepType1"
}
@serializable // error, property className is missing
class OopsNoClassName {
}
@serializable // error, no no-arg constructor
class OopsConstructorRequiresArguments {
readonly className = "OopsConstructorRequiresArguments"
constructor(arg: any) {
}
}
@serializable
class StepType2 implements IStep {
id: number = 0;
name: string = "";
prop2: string = "";
prop3: string = "";
prop4: string = "";
readonly className = "StepType2"
}
@serializable
class StepType3 implements IStep {
id: number = 0;
name: string = "";
prop5: string = "";
prop6: string = "";
readonly className = "StepType3"
}
现在让我们测试一下。像往常一样制作一些对象,并将它们放入一个数组中:
// create some objects of our classes
const stepType1 = new StepType1();
stepType1.id = 1;
stepType1.name = "Alice";
stepType1.prop1 = "apples";
const stepType2 = new StepType2();
stepType2.id = 2;
stepType2.name = "Bob";
stepType2.prop2 = "bananas";
stepType2.prop3 = "blueberries";
stepType2.prop4 = "boysenberries";
const stepType3 = new StepType3();
stepType3.id = 3;
stepType3.name = "Carol";
stepType3.prop5 = "cherries";
stepType3.prop6 = "cantaloupes";
// make an array of IStep[]
const arr = [stepType1, stepType2, stepType3];
让我们有一个函数来检查数组的元素并检查它们是否是你的类的实例:
// verify that an array of IStep[] contains class instances
function verifyArray(arr: IStep[]) {
console.log("Array contents:\n" + arr.map(a => {
const constructorName = (a instanceof StepType1) ? "StepType1" :
(a instanceof StepType2) ? "StepType2" :
(a instanceof StepType3) ? "StepType3" : "???"
return ("id=" + a.id + ", name=" + a.name + ", instanceof " + constructorName)
}).join("\n") + "\n");
}
让我们确保它适用于arr:
// before serialization, everything is fine
verifyArray(arr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
然后我们序列化它:
// serialize to JSON
const json = JSON.stringify(arr);
为了演示您最初的问题,让我们看看如果我们只使用 JSON.parse() 而不使用 reviver 会发生什么:
// try to deserialize with just JSON.parse
const badParsedArr = JSON.parse(json) as IStep[];
// uh oh, none of the deserialized objects are actually class instances
verifyArray(badParsedArr);
// Array contents:
// id=1, name=Alice, instanceof ???
// id=2, name=Bob, instanceof ???
// id=3, name=Carol, instanceof ???
如您所见,badParsedArr 中的对象确实具有 id 和 name 属性(以及其他特定于类的实例属性,如 prop3,如果您选中),但它们不是您的类的实例.
现在我们可以通过使用我们的自定义反序列化器来查看问题是否得到解决:
// do the deserialization with our custom deserializer
const goodParsedArr = deserializeJSON(json) as IStep[];
// now everything is fine again
verifyArray(goodParsedArr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
是的,它有效!
上述方法很好,但有一些警告。主要的:如果您的可序列化类包含本身可序列化的属性,只要您的对象图是tree,其中每个对象恰好出现一次,它就会起作用。但是,如果您有一个对象图,其中包含任何类型的cycle(这意味着如果您以多种方式遍历该图,同一个对象会出现多次),那么您将得到意想不到的结果。例如:
const badArr = [stepType1, stepType1];
console.log(badArr[0] === badArr[1]); // true, same object twice
const badArrParsed = deserializeJSON(JSON.stringify(badArr));
console.log(badArrParsed[0] === baddArrParsed[1]); // false, two different objects
在上述情况下,同一个对象出现多次。当您序列化和反序列化数组时,您的新数组包含两个具有相同属性值的 不同 对象。如果你需要确保你只反序列化任何特定对象一次,那么你需要一个更复杂的deserialize() 函数来跟踪一些独特的属性(如id)并返回现有对象而不是创建新对象。
其他注意事项:假设您的可序列化类的实例属性仅由其他可序列化类以及 JSON 友好值(如字符串、数字、数组、普通对象和 null)组成。如果你使用其他东西,比如Dates,你将不得不处理那些序列化为字符串的事实。
序列化/反序列化对您来说究竟有多复杂在很大程度上取决于您的用例。
好的,希望对您有所帮助。祝你好运!