Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
90 changes: 89 additions & 1 deletion test/node/bigint.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BSON, BSONError } from '../register-bson';
import { BSON, EJSON, BSONError } from '../register-bson';
import { bufferFromHexArray } from './tools/utils';
import { expect } from 'chai';
import { BSON_DATA_LONG } from '../../src/constants';
Expand Down Expand Up @@ -263,4 +263,92 @@ describe('BSON BigInt support', function () {
expect(serializedMap).to.deep.equal(expectedSerialization);
});
});

describe('EJSON.stringify()', function () {
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 truncated 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('produces bigint strings that pass loose equality checks with native bigint values that are are 64 bits wide or less', function () {
const number = { a: 12345n };
const serialized = EJSON.stringify(number, { relaxed: false });
const parsed = JSON.parse(serialized);
// eslint-disable-next-line eqeqeq
expect(parsed.a.$numberLong == 12345n).true;
});

it('produces bigint strings that are equal to the strings generated when using BigInt.toString when the bigint values used are 64 bits wide or less', function () {
const number = { a: 12345n };
const serialized = EJSON.stringify(number, { relaxed: false });
const parsed = JSON.parse(serialized);
expect(parsed.a.$numberLong).to.equal(12345n.toString());
});

it('produces bigint strings that fail loose equality checks with native bigint values that are more than 64 bits wide', function () {
const number = { a: 0x1234_5678_1234_5678_9999n };
const serialized = EJSON.stringify(number, { relaxed: false });
const parsed = JSON.parse(serialized);
// eslint-disable-next-line eqeqeq
expect(parsed.a.$numberLong == 0x1234_5678_1234_5678_9999n).false;
});

it('produces bigint strings that are not equal to the strings generated when using BigInt.toString when the bigint values used are more than 64 bits wide', function () {
const number = { a: 0x1234_5678_1234_5678_9999n };
const serialized = EJSON.stringify(number, { relaxed: false });
const parsed = JSON.parse(serialized);
expect(parsed.a.$numberLong).to.not.equal(0x1234_5678_1234_5678_9999n.toString());
});
});
});