英文:
eval() class is not defined when imported?
问题
I see your code and the issue you're facing. The problem seems to be related to the use of eval()
in the reviverFromMap
function. When you import this function in another file, it can't resolve the class names like Position
. To fix this, you might want to ensure that the necessary classes are imported in the file where you're using reviverFromMap
. Here's the translated portion of your code:
我看到你的代码和你遇到的问题。问题似乎与`reviverFromMap`函数中的`eval()`使用有关。当你在另一个文件中导入这个函数时,它无法解析类名如`Position`。为了解决这个问题,你可以确保在使用`reviverFromMap`的文件中导入所需的类。以下是你的代码的翻译部分:
```ts
请在使用reviverFromMap
的文件中导入相关的类,以解决这个问题。
英文:
I'm trying to write a simple replacer/reviver combination that lets me serialize and deserialize ES6 classes properly (I need to deserialize them as ES6 classes, not Object
s) for a TypeScript project I'm working on.
I got it working easily enough by storing the class names in the JSON output under a special key/value pair (i.e. "$className":"Position"
).
The only problem is that the only way I could find to instantiate these classes later was to use:
const instance = eval(`new ${className}()`);
I was writing the rough draft for the code in a test file using Vitest to make sure it worked, and everything was working great until I was done and moved it to another file. If I import the reviver function that uses eval()
under the hood, it gives me the following error:
ReferenceError: Position is not defined
Note: Position
is an ES6 class in my test file
If I copy/paste the reviver code back in the .test.ts
file, the tests pass. If I remove that code and use import
, it complains about Position
not being defined.
I'm kind of confused about what might be causing this. Is it a known limitation of modules? Is it a TypeScript or Vite thing where the file imports mess with the hoisting, maybe? Is it an issue with eval()
when imported?
Any help would be appreciated.
Edit: Added full code below. Moving the contents of serialization.ts
before or after the class declarations in serialization.test.ts
doesn't cause the ReferenceError: Position is not defined
error.
serialization.ts
type JsonCallback = (key: string, value: any) => any;
export const replacerWithMap = (): [replacer: JsonCallback, idsToClassNames: Map<number, string>] => {
let counter = 0;
const classNamesToIds = new Map<string, number>();
const idsToClassNames = new Map<number, string>();
const replacer = (_: string, value: any): any => {
if (typeof value === 'object' && !Array.isArray(value) && !!value.constructor) {
const className = value.constructor.name;
if (!className) {
throw new Error(`Expected value to be class instance but was: ${value}`);
}
if (Object.hasOwn(value, '$_')) {
throw new Error(`Illegal property name "$_" found on object during serialization: ${className}`);
}
let id = classNamesToIds.get(className);
if (!id) {
id = counter;
classNamesToIds.set(className, id);
idsToClassNames.set(id, className);
++counter;
}
return {
'$_': id,
...value
};
}
return value;
}
return [replacer, idsToClassNames];
}
export const reviverFromMap = (idsToClassNames: Map<number, string>): JsonCallback => {
const reviver = (_: string, value: any): any => {
if (typeof value === 'object') {
if (Object.hasOwn(value, '$_')) {
const className = idsToClassNames.get(value.$_);
const instance = eval(`new ${className}()`); // <-------- eval() here
for (const [key, val] of Object.entries(value)) {
if (key !== '$_') {
instance[key] = val;
}
}
return instance;
}
}
return value;
}
return reviver;
}
serialization.test.ts
import { describe, expect, it } from 'vitest';
import { replacerWithMap, reviverFromMap } from './serialization';
class Position {
public x: number;
public y: number;
private _z: number;
public constructor(x: number, y: number) {
this.x = x;
this.y = y;
this._z = x * y;
}
public get z(): number {
return this._z;
}
public set z(value: number) {
this._z = value;
}
}
class Transform {
// Testing to see if it handles nested instances properly.
public position = new Position(3, 5);
public name: string = '';
}
describe('Serialization helpers', () => {
it('Serializes objects and maintains their class names in a map', () => {
// Arrange
const transform = new Transform();
transform.name = 'rotation';
// Act
const [replacer, idsToClassNames] = replacerWithMap();
const reviver = reviverFromMap(idsToClassNames);
const str = JSON.stringify(transform, replacer);
console.log(str, idsToClassNames);
const deserialized = JSON.parse(str, reviver) as Transform;
// Assert
expect(deserialized).toEqual(transform);
expect(deserialized).toBeInstanceOf(Transform);
expect(deserialized.position).toEqual(transform.position);
expect(deserialized.position).toBeInstanceOf(Position);
expect(deserialized.position.x).toBe(3);
expect(deserialized.position.y).toBe(5);
expect(deserialized.position.z).toBe(deserialized.position.y * deserialized.position.x);
});
});
答案1
得分: 1
是的。class
声明是模块作用域的,不是全局的,这正是模块的优势所在。因此,在serialization.ts中,由于Position
不在作用域内,eval
调用无法工作。你需要导入它,但当然你不会想导入所有应该在序列化库中可用的类。
而是,将类按名称作为参数传递给(反)序列化库,例如:
const classes = new Map(Object.entries({
Position,
Transform,
}));
const {replacer, reviver} = serialisation(classes);
英文:
> I'm kind of confused about what might be causing this. Is it a known limitation of modules?
Yes. The class
declaration is scoped to the module, it is not global, which is precisely the advantage of modules. So with Position
declared in serialization.test.ts, the eval
call in serialization.ts cannot work, as Position
is not in scope there! You would have needed to import
it, but of course you wouldn't want to import all the classes that should be usable in the serialisation library.
Instead, pass the classes, keyed by name, as a parameter to the (de)serialisation library, e.g.
const classes = new Map(Object.entries({
Position,
Transform,
}));
const {replacer, reviver} = serialisation(classes);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论