Skip to content
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
40 changes: 37 additions & 3 deletions src/generics/lib.generics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1949,14 +1949,48 @@ 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 {
return JsonValue.ofNumber(<number>value);
}
}

// 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<T, R = JsonValue>(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<S>(space: S): string {
if (typeof space == "string") {
Expand Down
17 changes: 17 additions & 0 deletions tests/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,22 @@ 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({ 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");
}

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));
Expand All @@ -163,6 +179,7 @@ function roundTrip() {
}

function main() {
objects();
parsing();
strings();
rejected();
Expand Down
Loading