Skip to content

Commit 465b16e

Browse files
Merge branch 'main' into NODE-4410-no-proto-keys
2 parents a66d037 + be74b30 commit 465b16e

File tree

3 files changed

+58
-45
lines changed

3 files changed

+58
-45
lines changed

docs/upgrade-to-v5.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,21 @@ BSON.deserialize(BSON.serialize(object));
112112
// now returns { a: 1 } in v5.0
113113
// would have returned { a: 1, b: 2 } in v4.x
114114
```
115+
116+
### Negative Zero is now serialized to Double
117+
118+
BSON serialize will now preserve negative zero values as a floating point number.
119+
120+
Previously it was required to use the `Double` type class to preserve `-0`:
121+
```ts
122+
BSON.deserialize(BSON.serialize({ d: -0 }))
123+
// no type preservation, returns { d: 0 }
124+
BSON.deserialize(BSON.serialize({ d: new Double(-0) }))
125+
// type preservation, returns { d: -0 }
126+
```
127+
128+
Now `-0` can be used directly
129+
```ts
130+
BSON.deserialize(BSON.serialize({ d: -0 }))
131+
// type preservation, returns { d: -0 }
132+
```

src/parser/serializer.ts

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -68,48 +68,39 @@ function serializeString(buffer: Uint8Array, key: string, value: string, index:
6868
return index;
6969
}
7070

71-
const SPACE_FOR_FLOAT64 = new Uint8Array(8);
72-
const DV_FOR_FLOAT64 = new DataView(
73-
SPACE_FOR_FLOAT64.buffer,
74-
SPACE_FOR_FLOAT64.byteOffset,
75-
SPACE_FOR_FLOAT64.byteLength
76-
);
71+
const NUMBER_SPACE = new DataView(new ArrayBuffer(8), 0, 8);
72+
const FOUR_BYTE_VIEW_ON_NUMBER = new Uint8Array(NUMBER_SPACE.buffer, 0, 4);
73+
const EIGHT_BYTE_VIEW_ON_NUMBER = new Uint8Array(NUMBER_SPACE.buffer, 0, 8);
74+
7775
function serializeNumber(buffer: Uint8Array, key: string, value: number, index: number) {
78-
// We have an integer value
79-
// TODO(NODE-2529): Add support for big int
80-
if (
81-
Number.isInteger(value) &&
82-
value >= constants.BSON_INT32_MIN &&
83-
value <= constants.BSON_INT32_MAX
84-
) {
85-
// If the value fits in 32 bits encode as int32
86-
// Set int type 32 bits or less
87-
buffer[index++] = constants.BSON_DATA_INT;
88-
// Number of written bytes
89-
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
90-
// Encode the name
91-
index = index + numberOfWrittenBytes;
92-
buffer[index++] = 0;
93-
// Write the int value
94-
buffer[index++] = value & 0xff;
95-
buffer[index++] = (value >> 8) & 0xff;
96-
buffer[index++] = (value >> 16) & 0xff;
97-
buffer[index++] = (value >> 24) & 0xff;
76+
const isNegativeZero = Object.is(value, -0);
77+
78+
const type =
79+
!isNegativeZero &&
80+
Number.isSafeInteger(value) &&
81+
value <= constants.BSON_INT32_MAX &&
82+
value >= constants.BSON_INT32_MIN
83+
? constants.BSON_DATA_INT
84+
: constants.BSON_DATA_NUMBER;
85+
86+
if (type === constants.BSON_DATA_INT) {
87+
NUMBER_SPACE.setInt32(0, value, true);
9888
} else {
99-
// Encode as double
100-
buffer[index++] = constants.BSON_DATA_NUMBER;
101-
// Number of written bytes
102-
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
103-
// Encode the name
104-
index = index + numberOfWrittenBytes;
105-
buffer[index++] = 0;
106-
// Write float
107-
DV_FOR_FLOAT64.setFloat64(0, value, true);
108-
buffer.set(SPACE_FOR_FLOAT64, index);
109-
// Adjust index
110-
index = index + 8;
89+
NUMBER_SPACE.setFloat64(0, value, true);
11190
}
11291

92+
const bytes =
93+
type === constants.BSON_DATA_INT ? FOUR_BYTE_VIEW_ON_NUMBER : EIGHT_BYTE_VIEW_ON_NUMBER;
94+
95+
buffer[index++] = type;
96+
97+
const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
98+
index = index + numberOfWrittenBytes;
99+
buffer[index++] = 0x00;
100+
101+
buffer.set(bytes, index);
102+
index += bytes.byteLength;
103+
113104
return index;
114105
}
115106

@@ -387,8 +378,8 @@ function serializeDouble(buffer: Uint8Array, key: string, value: Double, index:
387378
buffer[index++] = 0;
388379

389380
// Write float
390-
DV_FOR_FLOAT64.setFloat64(0, value.value, true);
391-
buffer.set(SPACE_FOR_FLOAT64, index);
381+
NUMBER_SPACE.setFloat64(0, value.value, true);
382+
buffer.set(EIGHT_BYTE_VIEW_ON_NUMBER, index);
392383

393384
// Adjust index
394385
index = index + 8;

test/node/double.test.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as BSON from '../register-bson';
22
const Double = BSON.Double;
33

4+
const BSON_DOUBLE_TYPE_INDICATOR = 0x01;
5+
const BSON_INT_TYPE_INDICATOR = 0x10;
6+
47
describe('BSON Double Precision', function () {
58
context('class Double', function () {
69
describe('constructor()', function () {
@@ -78,14 +81,15 @@ describe('BSON Double Precision', function () {
7881
});
7982
});
8083

81-
it('NODE-4335: does not preserve -0 in serialize-deserialize roundtrip if JS number is used', function () {
82-
// TODO (NODE-4335): -0 should be serialized as double
83-
// This test is demonstrating the behavior of -0 being serialized as an int32 something we do NOT want to unintentionally change, but may want to change in the future, which the above ticket serves to track.
84+
it('does preserve -0 in serialize as a double', function () {
8485
const value = -0;
8586
const serializedDouble = BSON.serialize({ d: value });
8687
const type = serializedDouble[4];
87-
expect(type).to.not.equal(BSON.BSON_DATA_NUMBER);
88-
expect(type).to.equal(BSON.BSON_DATA_INT);
88+
expect(type).to.equal(BSON_DOUBLE_TYPE_INDICATOR);
89+
expect(type).to.not.equal(BSON_INT_TYPE_INDICATOR);
90+
expect(serializedDouble.subarray(7, 15)).to.deep.equal(
91+
new Uint8Array(new Float64Array([-0]).buffer)
92+
);
8993
});
9094

9195
describe('extended JSON', () => {

0 commit comments

Comments
 (0)