在 JSON 之间序列化 BigDecimal、BigNumber、BigInt 等

Serialise BigDecimal, BigNumber, BigInt etc. to and from JSON

我可以使用什么类型和什么序列化模式来确保我的 JavaScript 程序在 运行 时具有 任意精度数字 ,但是 状态被序列化到 JSON 或从 JSON 反序列化,痛苦最小?

我正在编写一个应用程序,它大量使用精度高于 ECMAScript 本机 Number 类型所允许的精度的数字。所以我需要使用自定义类型(目前 BigNumber)来表示这些值。

我非常期待提议的 BigInt 类型,当它作为标准实现时,将对许多应用程序(包括我正在编写的应用程序)有所改进。然而,这对 JSON 一点帮助都没有,它对 BigInt 的了解比对 BigNumber.

的了解更多

无论我选择哪种自定义类型,在整个应用程序状态中都有很多这种类型的值。如此之多以至于值得考虑在序列化/反序列化层中使用自定义挂钩来处理将其转换为 JSON.

大概 JSON 文档必须将值表示为 JSON 本机类型(例如 ObjectString 实例)。那么,在反序列化整个复杂应用程序状态时,如何可靠地识别所有其他实例并将这些实例反序列化为正确的 BigIntBigNumber 类型的正确值?

我如何序列化该状态,以便任何 BigNumber(或插入一些其他任意精度数字类型)值在序列化/反序列化过程中可靠地存活下来,以更正正确类型的值?

一个可能的解决方案是 the Granola library for Node.js

granola provides a JSON compatible stringifier and parser with support for modern language and object primitives.

这需要:

  • 将应用程序从使用 BigNumber 值转换为 BigInt 值。
  • 等待工具在整个构建工具链中支持BigInt

如果修改类型(通过向原型添加 属性)是可行的,则有一个 toJSON hook from JSON.stringify,专门用于帮助自定义类型与 JSON 序列化协作。

If an object being stringified has a property named toJSON whose value is a function, then the toJSON() method customizes JSON stringification behavior: instead of the object being serialized, the value returned by the toJSON() method when called will be serialized.

因此您可以向 BigNumber 添加一个新方法 class:

BigNumber.prototype.toJSON = function toJSON(key) {
    return {
        _type: 'BigNumber',
        _data: Object.assign({}, this),
    };
};

state = {
    lorem: true,
    ipsum: "Consecteur non dibale",
    dolor: new BigNumber(107.58),
    sit: { spam: 5, eggs: 6, beans: 7 },
    amet: false,
};
serialisedState = JSON.stringify(state);
console.debug("serialisedState:", serialisedState);
//  → '{"lorem":true,"ipsum":"Consecteur non dibale","dolor":{"_type":"BigNumber","_data":{"s":1,"e":2,"c":[1,0,7,5,8]}},"sit":{"spam":5,"eggs":6,"beans":7},"amet":false}'

然后您可以在反序列化时识别这些特定对象,使用 the reviver parameter of JSON.parse:

If a reviver is specified, the value computed by parsing is transformed before being returned. Specifically, the computed value and all its properties (beginning with the most nested properties and proceeding to the original value itself) are individually run through the reviver. Then it is called, with the object containing the property being processed as this, and with the property name as a string, and the property value as arguments. [If the return value is not undefined], the property is redefined to be the return value.

function reviveFromJSON(key, value) {
    let result = value;
    if (
        (typeof value === 'object' && value !== null)
            && (value.hasOwnProperty('_type'))) {
        switch (value._type) {
        case 'BigNumber':
            result = Object.assign(new BigNumber(0), value._data);
        }
    }
    return result;
}

state = JSON.parse(serialisedState, reviveFromJSON);
console.debug("state:", state);
// → { … dolor: BigNumber { s: 1, e: 2, c: [ 1, 0, 7, 5, 8 ] }, … }