Skip to content

Commit

Permalink
Make handling of long numerals an option that is disabled by default (#…
Browse files Browse the repository at this point in the history
…557) (#561)

Also:
* Strengthen the tests
* update USER_GUIDE.md


(cherry picked from commit 08069bc)

Signed-off-by: Miki <miki@amazon.com>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
1 parent c509675 commit 198d3a2
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 15 deletions.
16 changes: 16 additions & 0 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- [Authenticate with Amazon OpenSearch Service](#authenticate-with-amazon-opensearch-service)
- [Using AWS V2 SDK](#using-aws-v2-sdk)
- [Using AWS V3 SDK](#using-aws-v3-sdk)
- [Enable Handling of Long Numerals](#enable-handling-of-long-numerals)
- [Create an Index](#create-an-index)
- [Add a Document to the Index](#add-a-document-to-the-index)
- [Search for the Document](#search-for-the-document)
Expand Down Expand Up @@ -107,6 +108,21 @@ const client = new Client({
});
```

### Enable Handling of Long Numerals

JavaScript can safely work with integers from -(2<sup>53</sup> - 1) to 2<sup>53</sup> - 1. However,
serialized JSON texts from other languages can potentially have numeric values beyond that range and the native
serialization and deserialization methods of JavaScript's JSON, incapable of parsing them with precision; these
values get rounded to fit the IEEE-754 representation.

The `Client` can be configured to appropriately deserialize long numerals as `BigInt` values and vice versa:

```javascript
const client = new Client({
enableLongNumeralSupport: true,
});
```

## Create an Index

```javascript
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class Client extends OpenSearchAPI {
proxy: null,
enableMetaHeader: true,
disablePrototypePoisoningProtection: false,
enableLongNumeralSupport: false,
},
opts
);
Expand All @@ -151,6 +152,7 @@ class Client extends OpenSearchAPI {
this[kEventEmitter] = new EventEmitter();
this.serializer = new options.Serializer({
disablePrototypePoisoningProtection: options.disablePrototypePoisoningProtection,
enableLongNumeralSupport: options.enableLongNumeralSupport,
});
this.connectionPool = new options.ConnectionPool({
pingTimeout: options.pingTimeout,
Expand Down
1 change: 1 addition & 0 deletions lib/Serializer.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

export interface SerializerOptions {
disablePrototypePoisoningProtection: boolean | 'proto' | 'constructor';
enableLongNumeralSupport: boolean;
}

export default class Serializer {
Expand Down
13 changes: 9 additions & 4 deletions lib/Serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class Serializer {
this[kJsonOptions] = {
protoAction: disable === true || disable === 'proto' ? 'ignore' : 'error',
constructorAction: disable === true || disable === 'constructor' ? 'ignore' : 'error',
enableLongNumeralSupport: opts.enableLongNumeralSupport === true,
};
}

Expand Down Expand Up @@ -258,10 +259,12 @@ class Serializer {
}
return val;
};
const shouldHandleLongNumerals =
isBigIntSupported && this[kJsonOptions].enableLongNumeralSupport;
try {
json = JSON.stringify(object, isBigIntSupported ? checkForBigInts : null);
json = JSON.stringify(object, shouldHandleLongNumerals ? checkForBigInts : null);

if (isBigIntSupported && !numeralsAreNumbers) {
if (shouldHandleLongNumerals && !numeralsAreNumbers) {
const temp = this._stringifyWithBigInt(object, json);
if (temp) json = temp;
}
Expand All @@ -286,14 +289,16 @@ class Serializer {

return val;
};
const shouldHandleLongNumerals =
isBigIntSupported && this[kJsonOptions].enableLongNumeralSupport;
try {
object = sjson.parse(
json,
isBigIntSupported ? checkForLargeNumerals : null,
shouldHandleLongNumerals ? checkForLargeNumerals : null,
this[kJsonOptions]
);

if (isBigIntSupported && !numeralsAreNumbers) {
if (shouldHandleLongNumerals && !numeralsAreNumbers) {
const temp = this._parseWithBigInt(json);
if (temp) {
object = temp;
Expand Down
4 changes: 3 additions & 1 deletion test/fixtures/longnumerals-dataset.ndjson
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{"number":18014398509481982,"description":"-18014398509481982 , -1 , 1 , 18014398509481982"}
{"number":-18014398509481982,"description":"෴18014398509481982"}
{"number":-18014398509481982,"description":"෴߷֍෴18014398509481982"}
{"description":"[\"෴߷֍෴18014398509481982\"]", "number":18014398509481982}
{"description":"18014398509481982", "number":18014398509481982}
{"number":9007199254740891,"description":"Safer than [18014398509481982]"}
7 changes: 5 additions & 2 deletions test/integration/serializer/longnumerals.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const { Client } = require('../../../');
const INDEX = `test-serializer-${process.pid}`;
const client = new Client({
node: process.env.TEST_OPENSEARCH_SERVER || 'http://localhost:9200',
enableLongNumeralSupport: true,
});

beforeEach(async () => {
Expand Down Expand Up @@ -77,14 +78,16 @@ test('long numerals', async (t) => {
},
},
});
t.equal(results.length, 3);
t.equal(results.length, 5);
const object = {};
for (const result of results) {
object[result.description] = result.number;
}
t.same(object, {
'-18014398509481982 , -1 , 1 , 18014398509481982': 18014398509481982n,
'෴18014398509481982': -18014398509481982n,
'෴߷֍෴18014398509481982': -18014398509481982n,
'Safer than [18014398509481982]': 9007199254740891,
18014398509481982: 18014398509481982n,
'["෴߷֍෴18014398509481982"]': 18014398509481982n,
});
});
45 changes: 37 additions & 8 deletions test/unit/serializer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ test('Basic', (t) => {
t.same(s.deserialize(json), obj);
});

test('Long numerals', (t) => {
t.plan(7);
const s = new Serializer();
test('Long numerals enabled', (t) => {
t.plan(3);
const s = new Serializer({ enableLongNumeralSupport: true });
const longPositive = BigInt(Number.MAX_SAFE_INTEGER) * 2n; // eslint-disable-line no-undef
const longNegative = BigInt(Number.MIN_SAFE_INTEGER) * 2n; // eslint-disable-line no-undef
const json =
Expand All @@ -55,20 +55,49 @@ test('Long numerals', (t) => {
`"positive": ${longPositive.toString()}, ` +
`"array": [ ${longNegative.toString()}, ${longPositive.toString()} ], ` +
`"negative": ${longNegative.toString()},` +
`"false-positive-1": "෴${longNegative.toString()}", ` +
`"false-positive-2": "[ ߷${longPositive.toString()} ]", ` +
`"false-positive-3": "\\": ֍${longPositive.toString()}\\"", ` +
`"false-positive-4": "෴߷֍${longPositive.toString()}", ` +
`"hardcoded": 102931203123987` +
`}`;
const obj = s.deserialize(json);
const res = s.serialize(obj);
t.equal(obj.positive, longPositive);
t.equal(obj.negative, longNegative);
t.same(obj.array, [longNegative, longPositive]);
t.same(obj, {
hardcoded: 102931203123987,
'false-positive-4': `෴߷֍${longPositive.toString()}`,
'false-positive-3': `": ֍${longPositive.toString()}"`,
'false-positive-2': `[ ߷${longPositive.toString()} ]`,
'false-positive-1': `෴${longNegative.toString()}`,
negative: longNegative,
array: [longNegative, longPositive],
positive: longPositive,
['":' + longPositive]: `[ ${longNegative.toString()}, ${longPositive.toString()} ]`,
});
// The space before and after the values, and the lack of spaces before comma are intentional
t.equal(obj['":' + longPositive], `[ ${longNegative.toString()}, ${longPositive.toString()} ]`);
t.equal(obj.hardcoded, 102931203123987);
t.equal(res.replace(/\s+/g, ''), json.replace(/\s+/g, ''));
t.match(res, `"[ ${longNegative.toString()}, ${longPositive.toString()} ]"`);
});

test('long numerals not enabled', (t) => {
t.plan(5);
const s = new Serializer({ enableLongNumeralSupport: false });
const longPositive = BigInt(Number.MAX_SAFE_INTEGER) * 3n; // eslint-disable-line no-undef
const longNegative = BigInt(Number.MIN_SAFE_INTEGER) * 3n; // eslint-disable-line no-undef
const json =
`{` +
`"positive": ${longPositive.toString()}, ` +
`"negative": ${longNegative.toString()}` +
`}`;
const obj = s.deserialize(json);
const res = s.serialize(obj);
t.not(obj.positive, longPositive);
t.not(obj.negative, longNegative);
t.equal(typeof obj.positive, 'number');
t.equal(typeof obj.negative, 'number');
t.not(res.replace(/\s+/g, ''), json.replace(/\s+/g, ''));
});

test('ndserialize', (t) => {
t.plan(1);
const s = new Serializer();
Expand Down

0 comments on commit 198d3a2

Please sign in to comment.