Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(NODE-4873): support EJSON stringify from BigInt to $numberLong #547

Merged
merged 13 commits into from
Jan 18, 2023
Merged
7 changes: 7 additions & 0 deletions src/extended_json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,13 @@ function serializeValue(value: any, options: EJSONSerializeOptions): any {
return { $numberDouble: Object.is(value, -0) ? '-0.0' : value.toString() };
}

if (typeof value === 'bigint') {
if (!options.relaxed) {
return { $numberLong: BigInt.asIntN(64, value).toString() };
}
return Number(BigInt.asIntN(64, value));
}

if (value instanceof RegExp || isRegExp(value)) {
let flags = value.flags;
if (flags === undefined) {
Expand Down
57 changes: 57 additions & 0 deletions test/node/extended_json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as BSON from '../register-bson';
const EJSON = BSON.EJSON;
import * as vm from 'node:vm';
import { expect } from 'chai';
import { BSONDataView } from '../../src/utils/byte_utils';

// BSON types
const Binary = BSON.Binary;
Expand Down Expand Up @@ -180,6 +181,62 @@ describe('Extended JSON', function () {
expect(EJSON.parse(serialized)).to.deep.equal(numbers);
});

it('truncates bigint values when they are outside the range [BSON_INT64_MIN, BSON_INT64_MAX] in canonical mode', function () {
const numbers = { a: 2n ** 64n + 1n, b: -(2n ** 64n) - 1n };
const serialized = EJSON.stringify(numbers, { relaxed: false });
expect(serialized).to.equal('{"a":{"$numberLong":"1"},"b":{"$numberLong":"-1"}}');
});

it('truncates bigint values in the same way as BSON.serialize in canonical mode', function () {
const number = { a: 0x1234_5678_1234_5678_9999n };
const stringified = EJSON.stringify(number, { relaxed: false });
const serialized = BSON.serialize(number);

const VALUE_OFFSET = 7;
const dataView = BSONDataView.fromUint8Array(serialized);
const serializedValue = dataView.getBigInt64(VALUE_OFFSET, true);
const parsed = JSON.parse(stringified);

expect(parsed).to.have.property('a');
expect(parsed['a']).to.have.property('$numberLong');
expect(parsed.a.$numberLong).to.equal(0x5678_1234_5678_9999n.toString());

expect(parsed.a.$numberLong).to.equal(serializedValue.toString());
});

it('truncates bigint values in the same way as BSON.serialize in relaxed mode', function () {
const number = { a: 0x1234_0000_1234_5678_9999n }; // Ensure that the serialized number can be exactly represented as a JS number
const stringified = EJSON.stringify(number, { relaxed: true });
const serializedDoc = BSON.serialize(number);

const VALUE_OFFSET = 7;
const dataView = BSONDataView.fromUint8Array(serializedDoc);
const parsed = JSON.parse(stringified);

expect(parsed).to.have.property('a');
expect(parsed.a).to.equal(0x0000_1234_5678_9999);

expect(parsed.a).to.equal(Number(dataView.getBigInt64(VALUE_OFFSET, true)));
});

it('serializes bigint values to numberLong in canonical mode', function () {
const number = { a: 2n };
const serialized = EJSON.stringify(number, { relaxed: false });
expect(serialized).to.equal('{"a":{"$numberLong":"2"}}');
});

it('serializes bigint values to Number in relaxed mode', function () {
const number = { a: 10000n };
const serialized = EJSON.stringify(number, { relaxed: true });
expect(serialized).to.equal('{"a":10000}');
});

it('loses precision when serializing bigint values outside of range [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER] in relaxed mode', function () {
const numbers = { a: -(2n ** 53n) - 1n, b: 2n ** 53n + 2n };
const serialized = EJSON.stringify(numbers, { relaxed: true });
expect(serialized).to.equal('{"a":-9007199254740992,"b":9007199254740994}');
});

it('should correctly parse null values', function () {
expect(EJSON.parse('null')).to.be.null;
expect(EJSON.parse('[null]')[0]).to.be.null;
Expand Down