我可以通过从 JSON 反序列化来创建键值对映射吗?

Can I create a map of key-value pairs by deserializing from JSON?

提问人:sbridewell 提问时间:11/1/2023 最后编辑:sbridewell 更新时间:11/5/2023 访问量:58

问:

我正在尝试通过反序列化 JSON 文档来创建一个实例,但我实际得到的似乎是一种不同类型的对象,没有任何方法。Map<TKey, TValue>Map<TKey, TValue>

我对 TypeScript 很陌生(我主要使用 C#),所以我使用 Jest 创建了一个小型单元测试来检查是否能完成我想要的操作,特别是我是否可以使用该方法来遍历每个键值对。Map<TKey, TValue>forEach

function createMap(): Map<number, string> {
    const map = new Map<number, string>();
    map.set(1, 'foo');
    map.set(2, 'bar');
    console.log(map); // Console output: Map(2) { 1 => 'foo', 2 => 'bar' }
    return map;
}

function iterateUsingForEach(map: Map<number, string>): number {
    let counter = 0;
    map.forEach((value: string, key: number, map: Map<number, string>) => {
        // Normally I'd do something more exciting here than just count the
        // elements, but in the interests of simplicity...
        counter++;
    });
    return counter;
}

describe('A Map instantiated using its constructor', () => {
    it('can be iterated using map.forEach()', () => {
        const map = createMap();
        const count = iterateUsingForEach(map);
        expect(count).toBe(2); // pass
    });
});

到目前为止一切顺利,测试通过了,我有一个键值对列表,可以遍历每个键值对,这就是我想要的。但是填充此对象的数据来自对 Web API 的调用,该调用返回一个 JSON 文档,我需要将其反序列化为 ,因此我添加了更多测试来模拟此用例:Map<number, string>

function deserializeMap(): Map<number, string> {
    const json = '{ "1": "foo", "2": "bar" }';
    const map: Map<number, string> = JSON.parse(json) /*as Map<number, string>*/;
    console.log(map); // Console output: { '1': 'foo', '2': 'bar' }
    return map;
}

function iterateUsingObjectDotEntries(map: Map<number, string>): number {
    let count = 0;
    for (let [key, value] of Object.entries(map)) {
        count++;
    }
    return count;
}

describe('A Map instantiated using deserialization', () => {
    it('can be iterated using map.forEach()', () => {
        const map = deserializeMap();
        const count = iterateUsingForEach(map); // fail - TypeError: map.forEach is not a function
        expect(count).toBe(2);
    });

    it('can be iterated using Object.entries(map)', () => {
        const map = deserializeMap();
        const count = iterateUsingObjectDotEntries(map);
        expect(count).toBe(2); // pass
    });
});

第一个测试失败了,因为 ,并且查看控制台输出,似乎这次实际上根本不是一个实例,而是一个具有名为 和 的属性的普通对象,它可能没有原型,因此没有方法,尽管该函数显式声明其返回类型为,甚至尝试将其返回值转换为该类型(我不确定该强制转换是否有任何影响 - 编辑根据 jcalz 的评论,我已经评论了“cast”,因为它实际上不是一个 cast,而是一个类型断言)。map.forEach is not a functionmapMap<number, string>12deserializeMapMap<number, string>

我猜 TypeScript 编译器执行的类型检查并没有抱怨这一点,因为即使它实际上不是该类型的实例,它仍然与类型兼容mapMap<number, string>

第二个测试通过,因为它没有将对象视为键值对列表,而是循环访问每个属性名称和值。从功能上讲,使用可以做我想要的,但我觉得它类似于使用 .net 在运行时发现对象属性的名称和值,所以我想知道这是否会对性能产生影响。因此,我添加了这个基准来比较这两种方法的性能:mapObject.entries(map)Reflection

// Requires the tinybench package, to install it run:
// npm install tinybench --save-dev
// Also requires the following import:
// import { Bench } from 'tinybench';
it('runs a benchmark to compare the performance of the two approaches', async () => {
    const bench = new Bench({ iterations: 10000 });
    const instantiated = createMap();
    const deserialized = deserializeMap();

    bench
        .add('map.forEach()', () => {
            iterateUsingForEach(instantiated);
        })
        .add('Object.entries(map)', () => {
            iterateUsingObjectDotEntries(deserialized);
        });

    await bench.run();
    console.table(bench.table());
});

我已经运行了好几次,结果各不相同,但似乎需要 1.5 到 2 倍的时间来完成相同的工作。输出示例:Object.entries(map)map.forEach()

    ┌─────────┬───────────────────────┬─────────────┬───────────────────┬──────────┬─────────┐
    │ (index) │       Task Name       │   ops/sec   │ Average Time (ns) │  Margin  │ Samples │
    ├─────────┼───────────────────────┼─────────────┼───────────────────┼──────────┼─────────┤
    │    0    │    'map.forEach()'    │ '1,739,642' │ 574.8305975247757 │ '±8.27%' │ 869822  │
    │    1    │ 'Object.entries(map)' │ '1,074,330' │ 930.8122055151725 │ '±1.02%' │ 537167  │
    └─────────┴───────────────────────┴─────────────┴───────────────────┴──────────┴─────────┘

所以我的问题是,我是否可以将 JSON 文档反序列化为 的实际实例,包括其原型和方法,而不是仅与该类型类型兼容的对象,以便利用更快的性能?Map<number, string>map.forEach()

编辑:我不认为这是从后端接收到打字稿中前端接口的投射数据的重复,因为这似乎是关于将具有一个形状的对象的属性映射到具有不同形状的不同对象的属性,而我正在尝试从具有原型和方法的 JSON 创建一个对象。

javascript typescript 性能 json 反序列化

评论

3赞 merryweather 11/1/2023
任何 POJO(普通旧 JavaScript 对象)都不是 Map。
1赞 sbridewell 11/1/2023
感谢@jcalz我删除了类型断言,显然我误解了关键字的用途。as
2赞 jcalz 11/1/2023
JSON.parse()除非您传入类似函数之类的东西作为第二个参数,否则不会产生 s。因此,影响性能的唯一方法是更好地实现该功能(也许可能会更快)。这与 TypeScript 本身关系不大;它只是 JavaScript。如果你在这里寻找最佳性能,你可能想添加关于 JavaScript 和性能的标签,否则答案只是“没有内置的方法来做到这一点,你必须实现它”。我们应该如何进行?Mapiterate...for (let k in obj) {...}
1赞 sbridewell 11/1/2023
我正在寻找一种方法来重现像 .net 库 Json.NET 将 JSON 文档反序列化为所请求类型的实际实例的方式,而不是某种相同形状的对象,我认为 TypeScript 中的类型检查会更容易。这不是一个纯粹的性能问题,更多的是试图避免养成编写性能不佳的代码的习惯。我会考虑如何编辑问题来澄清这一点,我会做一些关于 reviver 和其他迭代对象的方法的搜索。
2赞 jcalz 11/1/2023
TypeScript 基本上没有运行时效果;它只是试图使编写 JavaScript 更容易。所以 JavaScript 就是你在这里要问的,而 TypeScript 有点像一条红鲱鱼(无论你注释或断言什么类型,运行时行为都不会受到影响)。没有内置功能可以将 s 序列化/反序列化为 JSON 或从 JSON 序列化。您可以通过将回调传递给 和 来自定义行为,也可以只使用默认行为,然后自己处理结果。真的没有区别MapJSON.stringify()JSON.parse()

答: 暂无答案