From 72071e36e187e20e18f582fd38ce86e1dca61dc3 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Thu, 12 Nov 2026 21:03:17 +0000 Subject: [PATCH 1/2] JSON.stringify writes an object literal or a tuple `JSON.stringify({x: 1})` threw a TypeError ("cannot read the fields of an object literal or a tuple"), which, uncaught, ended the program with 0xC0000409 and no output. The compiler now makes `for...in` over an object literal or a tuple for each field of its type, with the key a literal, so `value[key]` is that field with its own type; toValue walks the fields with it. A tuple of unnamed fields is an array, as in JavaScript; a function or an undefined field is left out of an object. A BigInt is still a TypeError. Needs the compiler change in TypeScriptCompiler (for...in over fields, and a generic's instance making other instances: `JSON.stringify([[1], [2]])` failed to compile in a program that had not stringified a number first). Co-Authored-By: Claude Opus 5.5 --- src/generics/lib.generics.ts | 40 +++++++++++++++++++++++++++++++++--- tests/json.ts | 16 +++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/generics/lib.generics.ts b/src/generics/lib.generics.ts index 322dba2..064ed73 100644 --- a/src/generics/lib.generics.ts +++ b/src/generics/lib.generics.ts @@ -1913,8 +1913,8 @@ namespace Promise { } // JSON.stringify, over JsonValue (src/lib.ts, with JSON.parse). Generic: it is told what it was given by -// the type each call is made with. There is no walking the fields of an object or a class at run time, -// so a class goes by its `toJSON()`, and an object literal or a tuple is a TypeError. +// the type each call is made with. An object literal's or a tuple's fields are walked by `for...in`, which +// the compiler makes for each field of the type; a class has no such walk and goes by its `toJSON()`. namespace JSON { // A value as a JsonValue. R: a signature is read before lib.ts declares JsonValue, a default type // only when the function is used. An else chain: code after an `if` the type settles is still @@ -1949,7 +1949,10 @@ namespace JSON { return toValue(value.toJSON()); } else if (typeof value == "tuple") { - throw new TypeError("JSON.stringify cannot read the fields of an object literal or a tuple: build a JsonValue"); + return fieldsToValue(value); + } else if (typeof value == "object") { + // an object literal with methods + return fieldsToValue(value); } else if (typeof value == "bigint") { throw new TypeError("JSON.stringify cannot serialize a BigInt"); } else { @@ -1957,6 +1960,37 @@ namespace JSON { } } + // An object literal or a tuple. `for...in` is made for each of its fields, `key` a literal, so + // `value[key]` is that field with its own type. A tuple of unnamed fields ("0", "1", ...) is an array, + // as in JavaScript; a function or an undefined field is left out of an object, and is null in an array. + function fieldsToValue(value: T): R { + if ("0" in value) { + const result = JsonValue.newArray(); + for (const key in value) { + if (typeof value[key] == "function") { + result.push(new JsonValue()); + } else { + result.push(toValue(value[key])); + } + } + + return result; + } else { + const result = JsonValue.newObject(); + for (const key in value) { + if (typeof value[key] == "function") { + // left out + } else if (typeof value[key] == "undefined") { + // left out + } else { + result.set(key, toValue(value[key])); + } + } + + return result; + } + } + // up to 10 of them, as in JavaScript function indent(space: S): string { if (typeof space == "string") { diff --git a/tests/json.ts b/tests/json.ts index e527b43..bb9d733 100644 --- a/tests/json.ts +++ b/tests/json.ts @@ -155,6 +155,21 @@ function chained() { assert(JSON.stringify(built) == `{"n":true,"list":["x",null],"empty":{},"added":"y"}`, "set again, chained"); } +// An object literal or a tuple: its fields are known from its type. Stringified first in its program, a +// value whose writing needs other instances of the writer (a number in an object, an array in an array) +// once failed to compile. +function objects() { + assert(JSON.stringify({ x: 1 }) == `{"x":1}`, "an object literal"); + const record = { s: "a\"b", b: true, n: null, list: [1, 2], empty: {} }; + assert(JSON.stringify(record) == `{"s":"a\\"b","b":true,"n":null,"list":[1,2],"empty":{}}`, "every kind of field"); + assert(record.s == "a\"b", "the object is only read"); + assert(JSON.stringify({ outer: { inner: [{ z: "q" }] } }) == `{"outer":{"inner":[{"z":"q"}]}}`, "nested"); + assert(JSON.stringify([{ a: 1 }, { a: 2 }]) == `[{"a":1},{"a":2}]`, "an array of object literals"); + assert(JSON.stringify({ k: 2, f: () => 1 }) == `{"k":2}`, "a function is left out"); + assert(JSON.stringify([1, "a", true]) == `[1,"a",true]`, "a tuple is an array"); + assert(JSON.stringify({ a: 1, b: [2] }, null, 2) == `{\n "a": 1,\n "b": [\n 2\n ]\n}`, "indented"); +} + function roundTrip() { const text = `{"s":"a\\"b\\u00e9","n":[1,-2.5,1e+21],"o":{"t":true,"f":false,"z":null}}`; const again = JSON.stringify(JSON.parse(text)); @@ -163,6 +178,7 @@ function roundTrip() { } function main() { + objects(); parsing(); strings(); rejected(); From 4bd4918378759b51084f738ae87164aa79d6661e Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Thu, 12 Nov 2026 21:35:14 +0000 Subject: [PATCH 2/2] json.ts: a value field named like an accessor's Co-Authored-By: Claude Opus 5.5 --- tests/json.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/json.ts b/tests/json.ts index bb9d733..bfff00c 100644 --- a/tests/json.ts +++ b/tests/json.ts @@ -166,6 +166,7 @@ function objects() { assert(JSON.stringify({ outer: { inner: [{ z: "q" }] } }) == `{"outer":{"inner":[{"z":"q"}]}}`, "nested"); assert(JSON.stringify([{ a: 1 }, { a: 2 }]) == `[{"a":1},{"a":2}]`, "an array of object literals"); assert(JSON.stringify({ k: 2, f: () => 1 }) == `{"k":2}`, "a function is left out"); + assert(JSON.stringify({ get_started: true, set_id: 5 }) == `{"get_started":true,"set_id":5}`, "names like an accessor's"); assert(JSON.stringify([1, "a", true]) == `[1,"a",true]`, "a tuple is an array"); assert(JSON.stringify({ a: 1, b: [2] }, null, 2) == `{\n "a": 1,\n "b": [\n 2\n ]\n}`, "indented"); }