Skip to content

Commit

Permalink
[Console] Add support for JSON with long numerals (#4562)
Browse files Browse the repository at this point in the history
Also:
* Add support for parsing and stringifying JSON with long numerals into `@osd/std`
* Upgrade to `@opensearch/[email protected]` which supports long numerals
* Add support for long numerals to `http/fetch`

Signed-off-by: Miki <[email protected]>
  • Loading branch information
AMoo-Miki committed Sep 26, 2023
1 parent 7ae32d6 commit 8ee7ff5
Show file tree
Hide file tree
Showing 34 changed files with 1,173 additions and 103 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Change color fn used to calculate icon colors for search typeahead suggestions ([#4884](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4884))
- [Next Theme] Make next theme the default ([#4854](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4854))
- [Vis Colors] Update color mapper to prioritize unique colors per vis ([#4890](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4890))
- [Console] Add support for JSON with long numerals ([#4562](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4562))
- Feat (Advanced Settings): Make new "Appearance" category ([#4845](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4845))
- Feat (home): Add new theme dashboard screenshots ([#4906](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4906))
- Modify call out text for discover ([#4932](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4932))
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
"@hapi/vision": "^6.1.0",
"@hapi/wreck": "^17.1.0",
"@opensearch-project/opensearch": "^1.1.0",
"@opensearch-project/opensearch-next": "npm:@opensearch-project/opensearch@^2.2.0",
"@opensearch-project/opensearch-next": "npm:@opensearch-project/opensearch@^2.3.1",
"@osd/ace": "1.0.0",
"@osd/analytics": "1.0.0",
"@osd/apm-config-loader": "1.0.0",
Expand Down
72 changes: 71 additions & 1 deletion packages/osd-std/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,73 @@
# `@osd/std` — OpenSearch Dashboards standard library

This package is a set of utilities that can be used both on server-side and client-side.
This package is a set of utilities that can be used both on server-side and client-side.

## API

#### `assertNever`

Can be used in switch statements to ensure we perform exhaustive checks.

#### `deepFreeze`

Apply `Object.freeze` to a value recursively and convert the return type to `Readonly` variant recursively.

#### `get`

Retrieve the value for the specified path of an object.

#### `getFlattenedObject`

Flatten a deeply nested object to a map of dot-separated paths, pointing to all of its primitive values and arrays.

#### `stringify` and `parse`

Drop-in replacement for `JSON.stringify` and `JSON.parse`, capable of handling long numerals and `BigInt` values.

#### `mapToObject`

Convert a map to an object.

#### `mapValuesOfMap`

Create a new `Map` populated with the results of calling a provided function on every element in the input `Map`.

#### `groupIntoMap`

Group elements of an `Array` into a `Map` based on a provided function.

#### `merge`

Deeply merge two objects, omitting undefined values, and not deeply merging arrays.

#### `pick`

Create a new `Object` of specified keys and their values from an input `Object`.

#### `withTimeout`

Apply a `timeout` duration to a `Promise` before throwing an `Error` with the provided message.

#### `firstValueFrom` and `lastValueFrom`

Get a `Promise` that resolves as soon as the first or last value arrives from an observable.

#### `unset`

Unset a (potentially nested) key from given object.

#### `modifyUrl`

Get an `Object` resulting from applying a provided function to the meaningful parts of a URL.

#### `isRelativeUrl`

Determine if a url is relative.

#### `getUrlOrigin`

Get the origin URL of a provided URL.

#### `validateObject`

Deeply validate that an `Object` does not contain any `__proto__` or `constructor.prototype` keys, or circular references.
19 changes: 19 additions & 0 deletions packages/osd-std/src/__snapshots__/json.test.ts.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/osd-std/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ export { unset } from './unset';
export { getFlattenedObject } from './get_flattened_object';
export { validateObject } from './validate_object';
export * from './rxjs_7';
export { parse, stringify } from './json';
176 changes: 176 additions & 0 deletions packages/osd-std/src/json.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { stringify, parse } from './json';

describe('json', () => {
it('can parse', () => {
const input = {
a: [
{ A: 1 },
{ B: '2' },
{ C: [1, 2, 3, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'] },
],
b: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
c: {
i: {},
ii: [],
iii: '',
iv: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
},
};
const result = parse(JSON.stringify(input));
expect(result).toEqual(input);
});

it('can stringify', () => {
const input = {
a: [
{ A: 1 },
{ B: '2' },
{ C: [1, 2, 3, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'] },
],
b: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
c: {
i: {},
ii: [],
iii: '',
iv: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
},
};
const result = stringify(input);
expect(result).toEqual(JSON.stringify(input));
});

it('can apply a reviver while parsing', () => {
const input = {
A: 255,
B: {
i: [[]],
ii: 'Lorem ipsum',
iii: {},
rand: Math.random(),
},
};
const text = JSON.stringify(input);
function reviver(this: any, key: string, val: any) {
if (Array.isArray(val) && toString.call(this) === '[object Object]') this._hasArrays = true;
else if (typeof val === 'string') val = `<![CDATA[${val}]]>`;
else if (typeof val === 'number') val = val.toString(16);
else if (toString.call(this) === '[object Object]' && key === 'rand' && val === input.B.rand)
this._found = true;
return val;
}

expect(parse(text, reviver)).toEqual(JSON.parse(text, reviver));
});

it('can apply a replacer and spaces while stringifying', () => {
const input = {
A: 255,
B: {
i: [[]],
ii: 'Lorem ipsum',
iii: {},
rand: Math.random(),
},
};

function replacer(this: any, key: string, val: any) {
if (Array.isArray(val) && val.length === 0) val.push('<empty>');
else if (typeof val === 'string') val = `<![CDATA[${val}]]>`;
else if (typeof val === 'number') val = val.toString(16);
else if (toString.call(this) === '[object Object]' && key === 'rand' && val === input.B.rand)
val = 1;
return val;
}

expect(stringify(input, replacer, 2)).toEqual(JSON.stringify(input, replacer, 2));
});

it('can handle long numerals while parsing', () => {
const longPositive = BigInt(Number.MAX_SAFE_INTEGER) * 2n;
const longNegative = BigInt(Number.MIN_SAFE_INTEGER) * 2n;
const text =
`{` +
// The space before and after the values, and the lack of spaces before comma are intentional
`"\\":${longPositive}": "[ ${longNegative.toString()}, ${longPositive.toString()} ]", ` +
`"positive": ${longPositive.toString()}, ` +
`"array": [ ${longNegative.toString()}, ${longPositive.toString()} ], ` +
`"negative": ${longNegative.toString()},` +
`"number": 102931203123987` +
`}`;

const result = parse(text);
expect(result.positive).toBe(longPositive);
expect(result.negative).toBe(longNegative);
expect(result.array).toEqual([longNegative, longPositive]);
expect(result['":' + longPositive]).toBe(
`[ ${longNegative.toString()}, ${longPositive.toString()} ]`
);
expect(result.number).toBe(102931203123987);
});

it('can handle BigInt values while stringifying', () => {
const longPositive = BigInt(Number.MAX_SAFE_INTEGER) * 2n;
const longNegative = BigInt(Number.MIN_SAFE_INTEGER) * 2n;
const input = {
[`": ${longPositive}`]: `[ ${longNegative.toString()}, ${longPositive.toString()} ]`,
positive: longPositive,
negative: longNegative,
array: [longNegative, longPositive],
number: 102931203123987,
};

expect(stringify(input)).toMatchSnapshot();
});

it('can apply a reviver on long numerals while parsing', () => {
const longPositive = BigInt(Number.MAX_SAFE_INTEGER) * 2n;
const longNegative = BigInt(Number.MIN_SAFE_INTEGER) * 2n;
const text =
`{` +
// The space before and after the values, and the lack of spaces before comma are intentional
`"\\":${longPositive}": "[ ${longNegative.toString()}, ${longPositive.toString()} ]", ` +
`"positive": ${longPositive.toString()}, ` +
`"array": [ ${longNegative.toString()}, ${longPositive.toString()} ], ` +
`"negative": ${longNegative.toString()},` +
`"number": 102931203123987` +
`}`;

const reviver = (key: string, val: any) => (typeof val === 'bigint' ? val * 3n : val);

const result = parse(text, reviver);
expect(result.positive).toBe(longPositive * 3n);
expect(result.negative).toBe(longNegative * 3n);
expect(result.array).toEqual([longNegative * 3n, longPositive * 3n]);
expect(result['":' + longPositive]).toBe(
`[ ${longNegative.toString()}, ${longPositive.toString()} ]`
);
expect(result.number).toBe(102931203123987);
});

it('can apply a replacer and spaces values while stringifying BigInts', () => {
const longPositive = BigInt(Number.MAX_SAFE_INTEGER) * 2n;
const longNegative = BigInt(Number.MIN_SAFE_INTEGER) * 2n;
const input = {
[`": ${longPositive}`]: `[ ${longNegative.toString()}, ${longPositive.toString()} ]`,
positive: longPositive,
negative: longNegative,
array: [longNegative, longPositive, []],
number: 102931203123987,
};

function replacer(this: any, key: string, val: any) {
if (typeof val === 'bigint') val = val * 3n;
else if (Array.isArray(val) && val.length === 0) val.push('<empty>');
else if (typeof val === 'string') val = `<![CDATA[${val}]]>`;
else if (typeof val === 'number') val = val.toString(16);
return val;
}

expect(stringify(input, replacer, 4)).toMatchSnapshot();
});
});
Loading

0 comments on commit 8ee7ff5

Please sign in to comment.