-
Notifications
You must be signed in to change notification settings - Fork 905
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Console] Add support for JSON with long numerals (#4562)
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
Showing
34 changed files
with
1,173 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); |
Oops, something went wrong.