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

[2.x][Console] Add support for JSON with long numerals (#4562) #5095

Merged
merged 1 commit into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading