Skip to content

Commit

Permalink
Update JSON validation logic (#47)
Browse files Browse the repository at this point in the history
* Fix JSON validation logic

* Remove debug value

* Skip sizing for validation

* Remove dead code and add array test

* Bump superstruct
  • Loading branch information
Mrtenz committed Nov 2, 2022
1 parent 6f888c7 commit 48feb84
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 95 deletions.
3 changes: 1 addition & 2 deletions package.json
Expand Up @@ -33,8 +33,7 @@
"dependencies": {
"@types/debug": "^4.1.7",
"debug": "^4.3.4",
"fast-deep-equal": "^3.1.3",
"superstruct": "^0.16.5"
"superstruct": "^0.16.7"
},
"devDependencies": {
"@lavamoat/allow-scripts": "^2.0.3",
Expand Down
39 changes: 3 additions & 36 deletions src/__fixtures__/json.ts
Expand Up @@ -1112,22 +1112,11 @@ export const COMPLEX_OBJECT = {
t: [true, true, true],
f: [false, false, false],
nulls: [null, null, null],
undef: undefined,
mixed: [
null,
undefined,
null,
undefined,
null,
true,
null,
false,
null,
undefined,
],
undef: null,
mixed: [null, null, null, null, null, true, null, false, null, null],
inObject: {
valueOne: null,
valueTwo: undefined,
valueTwo: null,
t: true,
f: false,
},
Expand Down Expand Up @@ -1159,25 +1148,3 @@ export const ARRAY_OF_DIFFRENT_KINDS_OF_NUMBERS = [
-5e-11, 5e-9, 0.000000000001, -0.00000000009, 100000.00000008, -100.88888,
0.333, 1000000000000,
];

export const ARRAY_OF_MIXED_SPECIAL_OBJECTS = [
null,
undefined,
null,
undefined,
undefined,
undefined,
null,
null,
null,
undefined,
];

export const OBJECT_MIXED_WITH_UNDEFINED_VALUES = {
a: undefined,
b: 'b',
c: undefined,
d: 'd',
e: undefined,
f: 'f',
};
33 changes: 14 additions & 19 deletions src/json.test.ts
@@ -1,7 +1,6 @@
import * as superstructModule from 'superstruct';
import {
ARRAY_OF_DIFFRENT_KINDS_OF_NUMBERS,
ARRAY_OF_MIXED_SPECIAL_OBJECTS,
COMPLEX_OBJECT,
JSON_FIXTURES,
JSON_RPC_ERROR_FIXTURES,
Expand All @@ -12,7 +11,6 @@ import {
JSON_RPC_RESPONSE_FIXTURES,
JSON_RPC_SUCCESS_FIXTURES,
NON_SERIALIZABLE_NESTED_OBJECT,
OBJECT_MIXED_WITH_UNDEFINED_VALUES,
} from './__fixtures__';
import {
assertIsJsonRpcFailure,
Expand Down Expand Up @@ -522,24 +520,15 @@ describe('json', () => {
]);
});

it('should return true for serialization and 2 for a size when only one key with undefined value is provided', () => {
it('should return false for serialization and 0 for a size when only one key with undefined value is provided', () => {
const valueToSerialize = {
a: undefined,
};

expect(validateJsonAndGetSize(valueToSerialize)).toStrictEqual([true, 2]);
});

it('should return true for serialization and 25 for a size when some of the values are undefined', () => {
expect(
validateJsonAndGetSize(OBJECT_MIXED_WITH_UNDEFINED_VALUES),
).toStrictEqual([true, 25]);
});

it('should return true for serialization and 17 for a size with mixed null and undefined in an array', () => {
expect(
validateJsonAndGetSize(ARRAY_OF_MIXED_SPECIAL_OBJECTS),
).toStrictEqual([true, 51]);
expect(validateJsonAndGetSize(valueToSerialize)).toStrictEqual([
false,
0,
]);
});

it('should return true for serialization and 73 for a size, for an array of numbers', () => {
Expand All @@ -548,10 +537,10 @@ describe('json', () => {
).toStrictEqual([true, 73]);
});

it('should return true for serialization and 1259 for a size of a complex nested object', () => {
it('should return true for serialization and 1288 for a size of a complex nested object', () => {
expect(validateJsonAndGetSize(COMPLEX_OBJECT)).toStrictEqual([
true,
1259,
1288,
]);
});

Expand Down Expand Up @@ -611,6 +600,12 @@ describe('json', () => {
]);
});

it('returns true for serialization and 37 for a size when checking an array', () => {
expect(
validateJsonAndGetSize(['foo', 'bar', null, ['foo', 'bar', null]]),
).toStrictEqual([true, 37]);
});

it('should return true or false for validity depending on the test scenario from ECMA TC39 (test262)', () => {
// This test will perform a series of validation assertions.
// These tests are taken from ECMA TC39 (test262) test scenarios used
Expand Down Expand Up @@ -761,7 +756,7 @@ describe('json', () => {
expect(validateJsonAndGetSize(false, true)).toStrictEqual([true, 0]);
expect(validateJsonAndGetSize('str', true)).toStrictEqual([true, 0]);
expect(validateJsonAndGetSize(123, true)).toStrictEqual([true, 0]);
expect(validateJsonAndGetSize(undefined, true)).toStrictEqual([true, 0]);
expect(validateJsonAndGetSize(undefined, true)).toStrictEqual([false, 0]);

// Value: string escape ASCII
const charToJson = {
Expand Down
39 changes: 7 additions & 32 deletions src/json.ts
@@ -1,19 +1,16 @@
import deepEqual from 'fast-deep-equal';
import {
array,
assert,
boolean,
define,
Infer,
integer,
is,
lazy,
literal,
nullable,
number,
object,
omit,
optional,
record,
string,
Struct,
union,
Expand All @@ -37,15 +34,10 @@ function isErrorWithMessage(error: unknown): error is { message: string } {
return typeof error === 'object' && error !== null && 'message' in error;
}

// Note: This struct references itself, so TypeScript cannot infer the type.
export const JsonStruct: Struct<Json> = union([
literal(null),
boolean(),
number(),
string(),
lazy(() => array(JsonStruct)),
lazy(() => record(string(), JsonStruct)),
]);
export const JsonStruct = define<Json>('Json', (value) => {
const [isValid] = validateJsonAndGetSize(value, true);
return isValid;
});

/**
* Any JSON-compatible value.
Expand All @@ -65,11 +57,7 @@ export type Json =
* @returns Whether the value is valid JSON.
*/
export function isValidJson(value: unknown): value is Json {
try {
return deepEqual(value, JSON.parse(JSON.stringify(value)));
} catch (_) {
return false;
}
return is(value, JsonStruct);
}

/**
Expand Down Expand Up @@ -512,8 +500,7 @@ export function validateJsonAndGetSize(
skipSizing: boolean,
): [isValid: boolean, plainTextSizeInBytes: number] {
if (value === undefined) {
// Return zero for undefined, since these are omitted from JSON serialization
return [true, 0];
return [false, 0];
} else if (value === null) {
// Return already specified constant size for null (special object)
return [true, skipSizing ? 0 : JsonSize.Null];
Expand Down Expand Up @@ -600,18 +587,6 @@ export function validateJsonAndGetSize(
return 0;
}

// If the size is 0, the value is undefined and undefined in an array
// when serialized will be replaced with null
if (size === 0 && Array.isArray(value)) {
size = JsonSize.Null;
}

// If the size is 0, that means the object is undefined and
// the rest of the object structure will be omitted
if (size === 0) {
return sum;
}

// Objects will have be serialized with "key": value,
// therefore we include the key in the calculation here
const keySize = Array.isArray(value)
Expand Down
11 changes: 5 additions & 6 deletions yarn.lock
Expand Up @@ -871,14 +871,13 @@ __metadata:
eslint-plugin-jsdoc: ^36.1.0
eslint-plugin-node: ^11.1.0
eslint-plugin-prettier: ^3.3.1
fast-deep-equal: ^3.1.3
jest: ^28.1.0
json-bigint: ^1.0.0
prettier: ^2.2.1
prettier-plugin-packagejson: ^2.2.11
rimraf: ^3.0.2
stdio-mock: ^1.2.0
superstruct: ^0.16.5
superstruct: ^0.16.7
ts-jest: ^28.0.8
tsd: ^0.24.1
typedoc: ^0.23.10
Expand Down Expand Up @@ -5782,10 +5781,10 @@ __metadata:
languageName: node
linkType: hard

"superstruct@npm:^0.16.5":
version: 0.16.5
resolution: "superstruct@npm:0.16.5"
checksum: 9f843c38695b584a605ae9b028629de18a85bd0dca0e9449b4ab98bb7b9ac3d82599870acbab9fbd2ee454c6b187af7e61562e252dfadabd974191ab4ab2e3ce
"superstruct@npm:^0.16.7":
version: 0.16.7
resolution: "superstruct@npm:0.16.7"
checksum: c8c855ff6945da8a41048c6d236de7b1af5d4d9c31742b3ee54d65647c31597488620281f65e095d5efc9e2fbdaad529b8c8f2506c12569d428467c835a21477
languageName: node
linkType: hard

Expand Down

0 comments on commit 48feb84

Please sign in to comment.