diff --git a/test/built-ins/JSON/isRawJSON/invoked-as-a-fn.js b/test/built-ins/JSON/isRawJSON/invoked-as-a-fn.js new file mode 100644 index 00000000000..8b9888dffce --- /dev/null +++ b/test/built-ins/JSON/isRawJSON/invoked-as-a-fn.js @@ -0,0 +1,42 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.israwjson +description: > + JSON.isRawJSON can be invoked as a function +info: | + JSON.isRawJSON ( O ) + + 1. If Type(O) is Object and O has an [[IsRawJSON]] internal slot, return true. + 2. Return false. + +includes: [deepEqual.js] +features: [json-parse-with-source] +---*/ + +function assertIsRawJson(rawJson, expectedRawJsonValue) { + assert.sameValue(null, Object.getPrototypeOf(rawJson)); + assert(Object.hasOwn(rawJson, 'rawJSON')); + assert.deepEqual(['rawJSON'], Object.getOwnPropertyNames(rawJson)); + assert.deepEqual([], Object.getOwnPropertySymbols(rawJson)); + assert.sameValue(expectedRawJsonValue, rawJson.rawJSON); +} + +assertIsRawJson(JSON.rawJSON(1), '1'); +assertIsRawJson(JSON.rawJSON(null), 'null'); +assertIsRawJson(JSON.rawJSON(true), 'true'); +assertIsRawJson(JSON.rawJSON(false), 'false'); +assertIsRawJson(JSON.rawJSON('"foo"'), '"foo"'); + + +const values = [1, 1.1, null, false, true, '123']; +for (const value of values) { + assert(!JSON.isRawJSON(value)); + assert(JSON.isRawJSON(JSON.rawJSON(value))); +} +assert(!JSON.isRawJSON(undefined)); +assert(!JSON.isRawJSON(Symbol('123'))); +assert(!JSON.isRawJSON([])); +assert(!JSON.isRawJSON({})); +assert(!JSON.isRawJSON({ rawJSON: '123' })); diff --git a/test/built-ins/JSON/parse/test-CreateJSONParseRecord.js b/test/built-ins/JSON/parse/test-CreateJSONParseRecord.js new file mode 100644 index 00000000000..0421702af08 --- /dev/null +++ b/test/built-ins/JSON/parse/test-CreateJSONParseRecord.js @@ -0,0 +1,220 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-createjsonparserecord +description: Codepaths involving CreateJSONParseRecord behave as expected + +includes: [deepEqual.js] +features: [json-parse-with-source] +---*/ + +function GenerateParseReviverFunction(texts) { + let i = 0; + return function (key, value, context) { + assert(typeof context === 'object'); + assert.sameValue(Object.prototype, Object.getPrototypeOf(context)); + // The json value is a primitive value, it's context only has a source property. + if (texts[i] !== undefined) { + const descriptor = Object.getOwnPropertyDescriptor(context, 'source'); + assert(descriptor.configurable); + assert(descriptor.enumerable); + assert(descriptor.writable); + assert.sameValue(undefined, descriptor.get); + assert.sameValue(undefined, descriptor.set); + assert.sameValue(texts[i++], descriptor.value); + + assert.deepEqual(['source'], Object.getOwnPropertyNames(context)); + assert.deepEqual([], Object.getOwnPropertySymbols(context)); + } else { + // The json value is JSArray or JSObject, it's context has no property. + assert(!Object.hasOwn(context, 'source')); + assert.deepEqual([], Object.getOwnPropertyNames(context)); + assert.deepEqual([], Object.getOwnPropertySymbols(context)); + i++; + } + return value; + }; +} + +// Test NumericLiteral +assert.sameValue(1, JSON.parse('1', GenerateParseReviverFunction(['1']))); +assert.sameValue(1.1, JSON.parse('1.1', GenerateParseReviverFunction(['1.1']))); +assert.sameValue(-1, JSON.parse('-1', GenerateParseReviverFunction(['-1']))); +assert.sameValue( + -1.1, + JSON.parse('-1.1', GenerateParseReviverFunction(['-1.1'])) +); +assert.sameValue( + 11, + JSON.parse('1.1e1', GenerateParseReviverFunction(['1.1e1'])) +); +assert.sameValue( + 11, + JSON.parse('1.1e+1', GenerateParseReviverFunction(['1.1e+1'])) +); +assert.sameValue( + 0.11, + JSON.parse('1.1e-1', GenerateParseReviverFunction(['1.1e-1'])) +); +assert.sameValue( + 11, + JSON.parse('1.1E1', GenerateParseReviverFunction(['1.1E1'])) +); +assert.sameValue( + 11, + JSON.parse('1.1E+1', GenerateParseReviverFunction(['1.1E+1'])) +); +assert.sameValue( + 0.11, + JSON.parse('1.1E-1', GenerateParseReviverFunction(['1.1E-1'])) +); + +assert.sameValue('1', JSON.stringify(JSON.rawJSON(1))); +assert.sameValue('1.1', JSON.stringify(JSON.rawJSON(1.1))); +assert.sameValue('-1', JSON.stringify(JSON.rawJSON(-1))); +assert.sameValue('-1.1', JSON.stringify(JSON.rawJSON(-1.1))); +assert.sameValue('11', JSON.stringify(JSON.rawJSON(1.1e1))); +assert.sameValue('0.11', JSON.stringify(JSON.rawJSON(1.1e-1))); + +// Test NullLiteral, BoolLiteral, StringLiteral +assert.sameValue( + null, + JSON.parse('null', GenerateParseReviverFunction(['null'])) +); +assert.sameValue( + true, + JSON.parse('true', GenerateParseReviverFunction(['true'])) +); +assert.sameValue( + false, + JSON.parse('false', GenerateParseReviverFunction(['false'])) +); +assert.sameValue( + 'foo', + JSON.parse('"foo"', GenerateParseReviverFunction(['"foo"'])) +); + +assert.sameValue('null', JSON.stringify(JSON.rawJSON(null))); +assert.sameValue('true', JSON.stringify(JSON.rawJSON(true))); +assert.sameValue('false', JSON.stringify(JSON.rawJSON(false))); +assert.sameValue('"foo"', JSON.stringify(JSON.rawJSON('"foo"'))); + +// Test ObjectLiteral +assert.deepEqual( + {}, + JSON.parse('{}', GenerateParseReviverFunction([])) +); +assert.deepEqual( + { 42: 37 }, + JSON.parse('{"42":37}', GenerateParseReviverFunction(['37'])) +); +assert.deepEqual( + { x: 1, y: 2 }, + JSON.parse('{"x": 1, "y": 2}', GenerateParseReviverFunction(['1', '2'])) +); +// undefined means the json value is JSObject or JSArray and the passed +// context to the reviver function has no source property. +assert.deepEqual( + { x: [1, 2], y: [2, 3] }, + JSON.parse( + '{"x": [1,2], "y": [2,3]}', + GenerateParseReviverFunction(['1', '2', undefined, '2', '3', undefined]) + ) +); +assert.deepEqual( + { x: { x: 1, y: 2 } }, + JSON.parse( + '{"x": {"x": 1, "y": 2}}', + GenerateParseReviverFunction(['1', '2', undefined, undefined]) + ) +); + +assert.sameValue('{"42":37}', JSON.stringify({ 42: JSON.rawJSON(37) })); +assert.sameValue( + '{"x":1,"y":2}', + JSON.stringify({ x: JSON.rawJSON(1), y: JSON.rawJSON(2) }) +); +assert.sameValue( + '{"x":{"x":1,"y":2}}', + JSON.stringify({ x: { x: JSON.rawJSON(1), y: JSON.rawJSON(2) } }) +); + +// Test ArrayLiteral +assert.deepEqual([1], JSON.parse('[1.0]', GenerateParseReviverFunction(['1.0']))); +assert.deepEqual( + [1.1], + JSON.parse('[1.1]', GenerateParseReviverFunction(['1.1'])) +); +assert.deepEqual([], JSON.parse('[]', GenerateParseReviverFunction([]))); +assert.deepEqual( + [1, '2', true, null, { x: 1, y: 1 }], + JSON.parse( + '[1, "2", true, null, {"x": 1, "y": 1}]', + GenerateParseReviverFunction(['1', '"2"', 'true', 'null', '1', '1']) + ) +); + +assert.sameValue('[1,1.1]', JSON.stringify([JSON.rawJSON(1), JSON.rawJSON(1.1)])); +assert.sameValue( + '["1",true,null,false]', + JSON.stringify([ + JSON.rawJSON('"1"'), + JSON.rawJSON(true), + JSON.rawJSON(null), + JSON.rawJSON(false), + ]) +); +assert.sameValue( + '[{"x":1,"y":1}]', + JSON.stringify([{ x: JSON.rawJSON(1), y: JSON.rawJSON(1) }]) +); + +// Test combinations of possible JSON input + +{ + let reviverCallIndex = 0; + const expectedKeys = ['a', 'b', 'c', '']; + const reviver = function(key, value, {source}) { + assert.sameValue(expectedKeys[reviverCallIndex++], key); + if (key == 'a') { + this.b = 2; + assert.sameValue('0', source); + } else if (key == 'b') { + this.c = 3; + assert.sameValue(2, value); + assert.sameValue(undefined, source); + } else if (key == 'c') { + assert.sameValue(3, value); + assert.sameValue(undefined, source); + } + return value; + } + assert.deepEqual({a: 0, b: 2, c: 3}, JSON.parse('{"a": 0, "b": 1, "c": [1, 2]}', reviver)); +} + +{ + let reviverCallIndex = 0; + const expectedKeys = ['0', '1', '2', '3', '']; + const reviver = function(key, value, {source}) { + assert.sameValue(expectedKeys[reviverCallIndex++], key); + if (key == '0') { + this[1] = 3; + assert.sameValue(1, value); + assert.sameValue('1', source); + } else if (key == '1') { + this[2] = 4; + assert.sameValue(3, value); + assert.sameValue(undefined, source); + } else if(key == '2') { + this[3] = 5; + assert.sameValue(4, value); + assert.sameValue(undefined, source); + } else if(key == '5'){ + assert.sameValue(5, value); + assert.sameValue(undefined, source); + } + return value; + } + assert.deepEqual([1, 3, 4, 5], JSON.parse('[1, 2, 3, {"a": 1}]', reviver)); +} diff --git a/test/staging/JSON/json-parse-with-source-snapshot.js b/test/built-ins/JSON/parse/test-InternaliseJSONProperty.js similarity index 74% rename from test/staging/JSON/json-parse-with-source-snapshot.js rename to test/built-ins/JSON/parse/test-InternaliseJSONProperty.js index 2a9692204c2..1fe38c974dd 100644 --- a/test/staging/JSON/json-parse-with-source-snapshot.js +++ b/test/built-ins/JSON/parse/test-InternaliseJSONProperty.js @@ -1,7 +1,10 @@ // Copyright (C) 2023 the V8 project authors. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. + /*--- -description: V8 mjsunit test for JSON.parse with source snapshotting +esid: sec-internalizejsonproperty +description: Codepaths involving InternaliseJSONProperty behave as expected + includes: [deepEqual.js] features: [json-parse-with-source] ---*/ @@ -11,7 +14,8 @@ const replacements = [42, {foo:'bar'}, 'foo']; -function TestArrayForwardModify(replacement) { +// Test Array forward modify +for (const replacement of replacements) { let alreadyReplaced = false; let expectedKeys = ['0','1','']; // lol who designed reviver semantics @@ -34,7 +38,8 @@ function TestArrayForwardModify(replacement) { assert.deepEqual([1, replacement], o); } -function TestObjectForwardModify(replacement) { +// Test Object forward modify +for (const replacement of replacements) { let alreadyReplaced = false; let expectedKeys = ['p','q','']; if (typeof replacement === 'object') { @@ -56,12 +61,9 @@ function TestObjectForwardModify(replacement) { assert.deepEqual({p:1, q:replacement}, o); } -for (const r of replacements) { - TestArrayForwardModify(r); - TestObjectForwardModify(r); -} -(function TestArrayAppend() { +// Test Array append +{ let log = []; const o = JSON.parse('[1,[]]', function (k, v, { source }) { log.push([k, v, source]); @@ -71,24 +73,25 @@ for (const r of replacements) { return this[k]; }); assert.deepEqual([['0', 1, '1'], - ['0', 'barf', undefined], - ['1', ['barf'], undefined], - ['', [1, ['barf']], undefined]], - log); -})(); + ['0', 'barf', undefined], + ['1', ['barf'], undefined], + ['', [1, ['barf']], undefined]], + log); +} -(function TestObjectAddProperty() { +// Test Object add property +{ let log = []; const o = JSON.parse('{"p":1,"q":{}}', function (k, v, { source }) { log.push([k, v, source]); if (v === 1) { this.q.added = 'barf'; } - return this[k]; + return this[k]; }); assert.deepEqual([['p', 1, '1'], - ['added', 'barf', undefined], - ['q', {added:'barf'}, undefined], - ['', {p:1, q:{added:'barf'}}, undefined]], - log); -})(); + ['added', 'barf', undefined], + ['q', {added:'barf'}, undefined], + ['', {p:1, q:{added:'barf'}}, undefined]], + log); +} diff --git a/test/built-ins/JSON/rawJSON/illegal-empty-and-start-end-chars.js b/test/built-ins/JSON/rawJSON/illegal-empty-and-start-end-chars.js new file mode 100644 index 00000000000..cfbf9ab93f7 --- /dev/null +++ b/test/built-ins/JSON/rawJSON/illegal-empty-and-start-end-chars.js @@ -0,0 +1,32 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.rawjson +description: > + JSON.rawJSON can be invoked as a function +info: | + JSON.rawJSON ( text ) + + 1. Let jsonString be ? ToString(text). + 2. Throw a SyntaxError exception if jsonString is the empty String, or if + either the first or last code unit of jsonString is any of 0x0009 + (CHARACTER TABULATION), 0x000A (LINE FEED), 0x000D (CARRIAGE RETURN), or + 0x0020 (SPACE). + +features: [json-parse-with-source] +---*/ + +const ILLEGAL_END_CHARS = ['\n', '\t', '\r', ' ']; +for (const char of ILLEGAL_END_CHARS) { + assert.throws(SyntaxError, () => { + JSON.rawJSON(`${char}123`); + }); + assert.throws(SyntaxError, () => { + JSON.rawJSON(`123${char}`); + }); +} + +assert.throws(SyntaxError, () => { + JSON.rawJSON(''); +}); diff --git a/test/built-ins/JSON/rawJSON/invalid-JSON-text.js b/test/built-ins/JSON/rawJSON/invalid-JSON-text.js new file mode 100644 index 00000000000..3efa68faa3d --- /dev/null +++ b/test/built-ins/JSON/rawJSON/invalid-JSON-text.js @@ -0,0 +1,35 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.rawjson +description: > + JSON.rawJSON can be invoked as a function +info: | + JSON.rawJSON ( text ) + + 1. Let jsonString be ? ToString(text). + ... + 3. Parse StringToCodePoints(jsonString) as a JSON text as specified in + ECMA-404. Throw a SyntaxError exception if it is not a valid JSON text as + defined in that specification, or if its outermost value is an object or + array as defined in that specification. + +features: [json-parse-with-source] +---*/ + +assert.throws(TypeError, () => { + JSON.rawJSON(Symbol('123')); +}); + +assert.throws(SyntaxError, () => { + JSON.rawJSON(undefined); +}); + +assert.throws(SyntaxError, () => { + JSON.rawJSON({}); +}); + +assert.throws(SyntaxError, () => { + JSON.rawJSON([]); +}); diff --git a/test/built-ins/JSON/rawJSON/invoked-as-a-fn-BigInt.js b/test/built-ins/JSON/rawJSON/invoked-as-a-fn-BigInt.js new file mode 100644 index 00000000000..7a82cf89a8b --- /dev/null +++ b/test/built-ins/JSON/rawJSON/invoked-as-a-fn-BigInt.js @@ -0,0 +1,31 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.rawjson +description: > + JSON.rawJSON can be invoked as a function +info: | + JSON.rawJSON ( text ) + + 1. Let jsonString be ? ToString(text). + ... + 4. Let internalSlotsList be « [[IsRawJSON]] ». + 5. Let obj be OrdinaryObjectCreate(null, internalSlotsList). + 6. Perform ! CreateDataPropertyOrThrow(obj, "rawJSON", jsonString). + 7. Perform ! SetIntegrityLevel(obj, frozen). + 8. Return obj. + +features: [json-parse-with-source] +---*/ + +const tooBigForNumber = BigInt(Number.MAX_SAFE_INTEGER) + 2n; +const intToBigInt = (key, val, { source }) => + typeof val === 'number' && val % 1 === 0 ? BigInt(source) : val; +const roundTripped = JSON.parse(String(tooBigForNumber), intToBigInt); +assert.sameValue(tooBigForNumber, roundTripped); + +const bigIntToRawJSON = (key, val) => + typeof val === 'bigint' ? JSON.rawJSON(val) : val; +const embedded = JSON.stringify({ tooBigForNumber }, bigIntToRawJSON); +assert.sameValue('{"tooBigForNumber":9007199254740993}', embedded); diff --git a/test/built-ins/JSON/rawJSON/invoked-as-a-fn.js b/test/built-ins/JSON/rawJSON/invoked-as-a-fn.js new file mode 100644 index 00000000000..9ba795ea3b7 --- /dev/null +++ b/test/built-ins/JSON/rawJSON/invoked-as-a-fn.js @@ -0,0 +1,57 @@ +// Copyright (C) 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-json.rawjson +description: > + JSON.rawJSON can be invoked as a function +info: | + JSON.rawJSON ( text ) + + 1. Let jsonString be ? ToString(text). + ... + 4. Let internalSlotsList be « [[IsRawJSON]] ». + 5. Let obj be OrdinaryObjectCreate(null, internalSlotsList). + 6. Perform ! CreateDataPropertyOrThrow(obj, "rawJSON", jsonString). + 7. Perform ! SetIntegrityLevel(obj, frozen). + 8. Return obj. + +features: [json-parse-with-source] +---*/ + +assert.sameValue('1', JSON.stringify(JSON.rawJSON(1))); +assert.sameValue('1.1', JSON.stringify(JSON.rawJSON(1.1))); +assert.sameValue('-1', JSON.stringify(JSON.rawJSON(-1))); +assert.sameValue('-1.1', JSON.stringify(JSON.rawJSON(-1.1))); +assert.sameValue('11', JSON.stringify(JSON.rawJSON(1.1e1))); +assert.sameValue('0.11', JSON.stringify(JSON.rawJSON(1.1e-1))); + +assert.sameValue('null', JSON.stringify(JSON.rawJSON(null))); +assert.sameValue('true', JSON.stringify(JSON.rawJSON(true))); +assert.sameValue('false', JSON.stringify(JSON.rawJSON(false))); +assert.sameValue('"foo"', JSON.stringify(JSON.rawJSON('"foo"'))); + +assert.sameValue('{"42":37}', JSON.stringify({ 42: JSON.rawJSON(37) })); +assert.sameValue( + '{"x":1,"y":2}', + JSON.stringify({ x: JSON.rawJSON(1), y: JSON.rawJSON(2) }) +); +assert.sameValue( + '{"x":{"x":1,"y":2}}', + JSON.stringify({ x: { x: JSON.rawJSON(1), y: JSON.rawJSON(2) } }) +); + +assert.sameValue('[1,1.1]', JSON.stringify([JSON.rawJSON(1), JSON.rawJSON(1.1)])); +assert.sameValue( + '["1",true,null,false]', + JSON.stringify([ + JSON.rawJSON('"1"'), + JSON.rawJSON(true), + JSON.rawJSON(null), + JSON.rawJSON(false), + ]) +); +assert.sameValue( + '[{"x":1,"y":1}]', + JSON.stringify([{ x: JSON.rawJSON(1), y: JSON.rawJSON(1) }]) +); diff --git a/test/staging/JSON/json-parse-with-source.js b/test/staging/JSON/json-parse-with-source.js deleted file mode 100644 index ff998e63640..00000000000 --- a/test/staging/JSON/json-parse-with-source.js +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright (C) 2023 the V8 project authors. All rights reserved. -// This code is governed by the BSD license found in the LICENSE file. -/*--- -description: V8 mjsunit test for JSON.parse with source -includes: [deepEqual.js] -features: [json-parse-with-source] ----*/ - -(function TestBigInt() { - const tooBigForNumber = BigInt(Number.MAX_SAFE_INTEGER) + 2n; - const intToBigInt = (key, val, { source }) => - typeof val === 'number' && val % 1 === 0 ? BigInt(source) : val; - const roundTripped = JSON.parse(String(tooBigForNumber), intToBigInt); - assert.sameValue(tooBigForNumber, roundTripped); - - const bigIntToRawJSON = (key, val) => - typeof val === 'bigint' ? JSON.rawJSON(val) : val; - const embedded = JSON.stringify({ tooBigForNumber }, bigIntToRawJSON); - assert.sameValue('{"tooBigForNumber":9007199254740993}', embedded); -})(); - -function GenerateParseReviverFunction(texts) { - let i = 0; - return function (key, value, context) { - assert(typeof context === 'object'); - assert.sameValue(Object.prototype, Object.getPrototypeOf(context)); - // The json value is a primitive value, it's context only has a source property. - if (texts[i] !== undefined) { - const descriptor = Object.getOwnPropertyDescriptor(context, 'source'); - assert(descriptor.configurable); - assert(descriptor.enumerable); - assert(descriptor.writable); - assert.sameValue(undefined, descriptor.get); - assert.sameValue(undefined, descriptor.set); - assert.sameValue(texts[i++], descriptor.value); - - assert.deepEqual(['source'], Object.getOwnPropertyNames(context)); - assert.deepEqual([], Object.getOwnPropertySymbols(context)); - } else { - // The json value is JSArray or JSObject, it's context has no property. - assert(!Object.hasOwn(context, 'source')); - assert.deepEqual([], Object.getOwnPropertyNames(context)); - assert.deepEqual([], Object.getOwnPropertySymbols(context)); - i++; - } - return value; - }; -} - -(function TestNumber() { - assert.sameValue(1, JSON.parse('1', GenerateParseReviverFunction(['1']))); - assert.sameValue(1.1, JSON.parse('1.1', GenerateParseReviverFunction(['1.1']))); - assert.sameValue(-1, JSON.parse('-1', GenerateParseReviverFunction(['-1']))); - assert.sameValue( - -1.1, - JSON.parse('-1.1', GenerateParseReviverFunction(['-1.1'])) - ); - assert.sameValue( - 11, - JSON.parse('1.1e1', GenerateParseReviverFunction(['1.1e1'])) - ); - assert.sameValue( - 11, - JSON.parse('1.1e+1', GenerateParseReviverFunction(['1.1e+1'])) - ); - assert.sameValue( - 0.11, - JSON.parse('1.1e-1', GenerateParseReviverFunction(['1.1e-1'])) - ); - assert.sameValue( - 11, - JSON.parse('1.1E1', GenerateParseReviverFunction(['1.1E1'])) - ); - assert.sameValue( - 11, - JSON.parse('1.1E+1', GenerateParseReviverFunction(['1.1E+1'])) - ); - assert.sameValue( - 0.11, - JSON.parse('1.1E-1', GenerateParseReviverFunction(['1.1E-1'])) - ); - - assert.sameValue('1', JSON.stringify(JSON.rawJSON(1))); - assert.sameValue('1.1', JSON.stringify(JSON.rawJSON(1.1))); - assert.sameValue('-1', JSON.stringify(JSON.rawJSON(-1))); - assert.sameValue('-1.1', JSON.stringify(JSON.rawJSON(-1.1))); - assert.sameValue('11', JSON.stringify(JSON.rawJSON(1.1e1))); - assert.sameValue('0.11', JSON.stringify(JSON.rawJSON(1.1e-1))); -})(); - -(function TestBasic() { - assert.sameValue( - null, - JSON.parse('null', GenerateParseReviverFunction(['null'])) - ); - assert.sameValue( - true, - JSON.parse('true', GenerateParseReviverFunction(['true'])) - ); - assert.sameValue( - false, - JSON.parse('false', GenerateParseReviverFunction(['false'])) - ); - assert.sameValue( - 'foo', - JSON.parse('"foo"', GenerateParseReviverFunction(['"foo"'])) - ); - - assert.sameValue('null', JSON.stringify(JSON.rawJSON(null))); - assert.sameValue('true', JSON.stringify(JSON.rawJSON(true))); - assert.sameValue('false', JSON.stringify(JSON.rawJSON(false))); - assert.sameValue('"foo"', JSON.stringify(JSON.rawJSON('"foo"'))); -})(); - -(function TestObject() { - assert.deepEqual( - {}, - JSON.parse('{}', GenerateParseReviverFunction([])) - ); - assert.deepEqual( - { 42: 37 }, - JSON.parse('{"42":37}', GenerateParseReviverFunction(['37'])) - ); - assert.deepEqual( - { x: 1, y: 2 }, - JSON.parse('{"x": 1, "y": 2}', GenerateParseReviverFunction(['1', '2'])) - ); - // undefined means the json value is JSObject or JSArray and the passed - // context to the reviver function has no source property. - assert.deepEqual( - { x: [1, 2], y: [2, 3] }, - JSON.parse( - '{"x": [1,2], "y": [2,3]}', - GenerateParseReviverFunction(['1', '2', undefined, '2', '3', undefined]) - ) - ); - assert.deepEqual( - { x: { x: 1, y: 2 } }, - JSON.parse( - '{"x": {"x": 1, "y": 2}}', - GenerateParseReviverFunction(['1', '2', undefined, undefined]) - ) - ); - - assert.sameValue('{"42":37}', JSON.stringify({ 42: JSON.rawJSON(37) })); - assert.sameValue( - '{"x":1,"y":2}', - JSON.stringify({ x: JSON.rawJSON(1), y: JSON.rawJSON(2) }) - ); - assert.sameValue( - '{"x":{"x":1,"y":2}}', - JSON.stringify({ x: { x: JSON.rawJSON(1), y: JSON.rawJSON(2) } }) - ); -})(); - -(function TestArray() { - assert.deepEqual([1], JSON.parse('[1.0]', GenerateParseReviverFunction(['1.0']))); - assert.deepEqual( - [1.1], - JSON.parse('[1.1]', GenerateParseReviverFunction(['1.1'])) - ); - assert.deepEqual([], JSON.parse('[]', GenerateParseReviverFunction([]))); - assert.deepEqual( - [1, '2', true, null, { x: 1, y: 1 }], - JSON.parse( - '[1, "2", true, null, {"x": 1, "y": 1}]', - GenerateParseReviverFunction(['1', '"2"', 'true', 'null', '1', '1']) - ) - ); - - assert.sameValue('[1,1.1]', JSON.stringify([JSON.rawJSON(1), JSON.rawJSON(1.1)])); - assert.sameValue( - '["1",true,null,false]', - JSON.stringify([ - JSON.rawJSON('"1"'), - JSON.rawJSON(true), - JSON.rawJSON(null), - JSON.rawJSON(false), - ]) - ); - assert.sameValue( - '[{"x":1,"y":1}]', - JSON.stringify([{ x: JSON.rawJSON(1), y: JSON.rawJSON(1) }]) - ); -})(); - -function assertIsRawJson(rawJson, expectedRawJsonValue) { - assert.sameValue(null, Object.getPrototypeOf(rawJson)); - assert(Object.hasOwn(rawJson, 'rawJSON')); - assert.deepEqual(['rawJSON'], Object.getOwnPropertyNames(rawJson)); - assert.deepEqual([], Object.getOwnPropertySymbols(rawJson)); - assert.sameValue(expectedRawJsonValue, rawJson.rawJSON); -} - -(function TestRawJson() { - assertIsRawJson(JSON.rawJSON(1), '1'); - assertIsRawJson(JSON.rawJSON(null), 'null'); - assertIsRawJson(JSON.rawJSON(true), 'true'); - assertIsRawJson(JSON.rawJSON(false), 'false'); - assertIsRawJson(JSON.rawJSON('"foo"'), '"foo"'); - - assert.throws(TypeError, () => { - JSON.rawJSON(Symbol('123')); - }); - - assert.throws(SyntaxError, () => { - JSON.rawJSON(undefined); - }); - - assert.throws(SyntaxError, () => { - JSON.rawJSON({}); - }); - - assert.throws(SyntaxError, () => { - JSON.rawJSON([]); - }); - - const ILLEGAL_END_CHARS = ['\n', '\t', '\r', ' ']; - for (const char of ILLEGAL_END_CHARS) { - assert.throws(SyntaxError, () => { - JSON.rawJSON(`${char}123`); - }); - assert.throws(SyntaxError, () => { - JSON.rawJSON(`123${char}`); - }); - } - - assert.throws(SyntaxError, () => { - JSON.rawJSON(''); - }); - - const values = [1, 1.1, null, false, true, '123']; - for (const value of values) { - assert(!JSON.isRawJSON(value)); - assert(JSON.isRawJSON(JSON.rawJSON(value))); - } - assert(!JSON.isRawJSON(undefined)); - assert(!JSON.isRawJSON(Symbol('123'))); - assert(!JSON.isRawJSON([])); - assert(!JSON.isRawJSON({ rawJSON: '123' })); -})(); - -(function TestReviverModifyJsonValue() { - { - let reviverCallIndex = 0; - const expectedKeys = ['a', 'b', 'c', '']; - const reviver = function(key, value, {source}) { - assert.sameValue(expectedKeys[reviverCallIndex++], key); - if (key == 'a') { - this.b = 2; - assert.sameValue('0', source); - } else if (key == 'b') { - this.c = 3; - assert.sameValue(2, value); - assert.sameValue(undefined, source); - } else if (key == 'c') { - assert.sameValue(3, value); - assert.sameValue(undefined, source); - } - return value; - } - assert.deepEqual({a: 0, b: 2, c: 3}, JSON.parse('{"a": 0, "b": 1, "c": [1, 2]}', reviver)); - } - { - let reviverCallIndex = 0; - const expectedKeys = ['0', '1', '2', '3', '']; - const reviver = function(key, value, {source}) { - assert.sameValue(expectedKeys[reviverCallIndex++], key); - if (key == '0') { - this[1] = 3; - assert.sameValue(1, value); - assert.sameValue('1', source); - } else if (key == '1') { - this[2] = 4; - assert.sameValue(3, value); - assert.sameValue(undefined, source); - } else if(key == '2') { - this[3] = 5; - assert.sameValue(4, value); - assert.sameValue(undefined, source); - } else if(key == '5'){ - assert.sameValue(5, value); - assert.sameValue(undefined, source); - } - return value; - } - assert.deepEqual([1, 3, 4, 5], JSON.parse('[1, 2, 3, {"a": 1}]', reviver)); - } -})();