JSON stringify 和 PostgreSQL bigint 合规性

JSON stringify and PostgreSQL bigint compliance

I am trying to add BigInt support within my library 和 运行 与 JSON.stringify 的问题。

库的性质允许不必担心类型歧义和反序列化,因为序列化的所有内容都会进入服务器,并且永远不需要任何反序列化。

我最初想出了以下简化方法,只是为了抵消 Node.js 向我投掷 TypeError: Do not know how to serialize a BigInt

// Does JSON.stringify, with support for BigInt:
function toJson(data) {
    return JSON.stringify(data, (_, v) => typeof v === 'bigint' ? v.toString() : v);
}

但由于它将每个 BigInt 转换为字符串,每个值最终都用双引号引起来。

是否有任何变通方法,也许是 Node.js 格式化实用程序中的一些技巧,可以从 JSON.stringify 中生成每个 BigInt 都被格式化为开放值的结果?这是 PostgreSQL 理解和支持的,所以我正在寻找一种方法来生成 JSON 和 BigInt,这与 PostgreSQL 兼容。

例子

const obj = {
    value: 123n
};

console.log(toJson(obj));

// This is what I'm getting: {"value":"123"}
// This is what I want: {"value":123}

显然,我不能只将 BigInt 转换为 number,因为那样我会丢失信息。为此重写整个 JSON.stringify 可能太复杂了。

更新

至此,我已经回顾并使用了几个 polyfill,比如这些:

但它们似乎都是一个笨拙的解决方案,引入这么多代码,然后修改以获得 BigInt 支持。我希望找到更优雅的东西。

我最终得到的解决方案...

注入完整的 123n 数字,然后在 RegEx 的帮助下取消引用:

function toJson(data) {
    return JSON.stringify(data, (_, v) => typeof v === 'bigint' ? `${v}n` : v)
        .replace(/"(-?\d+)n"/g, (_, a) => a);
}

它完全可以满足需要,而且速度很快。唯一的缺点是,如果您在 data 中将一个值设置为类似 123n 的字符串,它将成为一个开放数字,但您可以轻松地将其混淆为 [=16] =],或者123-bigint,算法很容易做到。

根据问题,该操作并不意味着是可逆的,因此如果您对结果使用 JSON.parse,则结果将是 number-s,丢失 [=20] 之间的任何内容=] 和 2^64 - 1,正如预期的那样。

谁说这是不可能的-嗯? :)

UPDATE-1

为了与 JSON.stringify 兼容,undefined 必须导致 undefined。在实际的 pg-promise 实现中,我现在使用 "123#bigint" 模式,以减少意外匹配的可能性。

所以这是最终代码:

 function toJson(data) {
    if (data !== undefined) {
        return JSON.stringify(data, (_, v) => typeof v === 'bigint' ? `${v}#bigint` : v)
            .replace(/"(-?\d+)#bigint"/g, (_, a) => a);
    }
}

UPDATE-2

通过下面的评论,您可以确保安全,方法是计算与 BigInt 注入匹配的替换次数,并在不匹配时抛出错误:

function toJson(data) {
    if (data !== undefined) {
        let intCount = 0, repCount = 0;
        const json = JSON.stringify(data, (_, v) => {
            if (typeof v === 'bigint') {
                intCount++;
                return `${v}#bigint`;
            }
            return v;
        });
        const res = json.replace(/"(-?\d+)#bigint"/g, (_, a) => {
            repCount++;
            return a;
        });
        if (repCount > intCount) {
            // You have a string somewhere that looks like "123#bigint";
            throw new Error(`BigInt serialization conflict with a string value.`);
        }
        return res;
    }
}

虽然我个人认为这是一种矫枉过正,但 UPDATE-1 中的方法已经足够好了。