eval()函数在导入时未定义。

huangapple go评论103阅读模式
英文:

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 Objects) 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);

huangapple
  • 本文由 发表于 2023年5月30日 04:28:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360163.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定