Skip to content

Commit 347330f

Browse files
committed
Add lib types for JSON.rawJSON, JSON.isRawJSON, and reviver context
Adds esnext.json.d.ts with type definitions for the Stage 4 TC39 proposal-json-parse-with-source (shipped in all major browsers and Node.js 22+): - JSON.rawJSON(text): creates a RawJSON object for lossless serialization - JSON.isRawJSON(value): type guard narrowing to RawJSON - JSON.parse reviver context: third parameter with { source: string } Fixes #61330
1 parent 9059e5b commit 347330f

File tree

8 files changed

+356
-0
lines changed

8 files changed

+356
-0
lines changed

src/compiler/commandLineParser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ const libEntries: [string, string][] = [
259259
["esnext.sharedmemory", "lib.esnext.sharedmemory.d.ts"],
260260
["esnext.temporal", "lib.esnext.temporal.d.ts"],
261261
["esnext.typedarrays", "lib.esnext.typedarrays.d.ts"],
262+
["esnext.json", "lib.esnext.json.d.ts"],
262263
// Decorators
263264
["decorators", "lib.decorators.d.ts"],
264265
["decorators.legacy", "lib.decorators.legacy.d.ts"],

src/lib/esnext.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
/// <reference lib="esnext.typedarrays" />
1010
/// <reference lib="esnext.temporal" />
1111
/// <reference lib="esnext.date" />
12+
/// <reference lib="esnext.json" />

src/lib/esnext.json.d.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Represents a "raw JSON" object created by `JSON.rawJSON()`.
3+
*
4+
* Raw JSON objects are frozen, null-prototype objects that carry pre-serialized
5+
* JSON text. When encountered by `JSON.stringify()`, the `rawJSON` property
6+
* value is emitted verbatim instead of the usual serialization.
7+
*
8+
* @see {@link https://tc39.es/proposal-json-parse-with-source/ TC39 proposal-json-parse-with-source}
9+
*/
10+
interface RawJSON {
11+
readonly rawJSON: string;
12+
}
13+
14+
interface JSON {
15+
/**
16+
* Converts a JavaScript Object Notation (JSON) string into an object.
17+
* @param text A valid JSON string.
18+
* @param reviver A function that transforms the results. This function is called for each member of the object.
19+
* If a member contains nested objects, the nested objects are transformed before the parent object is.
20+
* For primitive values the reviver also receives a `context` object whose `source` property is the original JSON
21+
* text of that value.
22+
* @throws {SyntaxError} If `text` is not valid JSON.
23+
*/
24+
parse(text: string, reviver: (this: any, key: string, value: any, context: { source: string }) => any): any;
25+
26+
/**
27+
* Creates a "raw JSON" object containing a piece of JSON text.
28+
* When serialized with `JSON.stringify()`, the raw text is emitted verbatim.
29+
* @param text A valid JSON string representing a primitive value (string, number, boolean, or null).
30+
* @throws {SyntaxError} If `text` is not valid JSON or represents an object or array.
31+
*/
32+
rawJSON(text: string): RawJSON;
33+
34+
/**
35+
* Returns whether the provided value is a raw JSON object created by `JSON.rawJSON()`.
36+
* @param value The value to test.
37+
*/
38+
isRawJSON(value: unknown): value is RawJSON;
39+
}

src/lib/libs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"esnext.sharedmemory",
9797
"esnext.temporal",
9898
"esnext.typedarrays",
99+
"esnext.json",
99100
"decorators",
100101
"decorators.legacy",
101102
// Default libraries
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [tests/cases/compiler/jsonParseWithSource.ts] ////
2+
3+
//// [jsonParseWithSource.ts]
4+
// JSON.rawJSON
5+
const raw = JSON.rawJSON("123");
6+
const rawStr: string = raw.rawJSON;
7+
JSON.stringify({ value: raw });
8+
JSON.stringify({ n: JSON.rawJSON("12345678901234567890") });
9+
10+
// JSON.isRawJSON
11+
const maybeRaw: unknown = {};
12+
if (JSON.isRawJSON(maybeRaw)) {
13+
const text: string = maybeRaw.rawJSON;
14+
}
15+
16+
// JSON.parse with reviver context
17+
JSON.parse('{"key":123}', (key, value, context) => {
18+
const src: string = context.source;
19+
return value;
20+
});
21+
22+
// Existing JSON.parse overloads still work
23+
JSON.parse("{}");
24+
JSON.parse('{"a":1}', (key, value) => value);
25+
26+
27+
//// [jsonParseWithSource.js]
28+
"use strict";
29+
// JSON.rawJSON
30+
const raw = JSON.rawJSON("123");
31+
const rawStr = raw.rawJSON;
32+
JSON.stringify({ value: raw });
33+
JSON.stringify({ n: JSON.rawJSON("12345678901234567890") });
34+
// JSON.isRawJSON
35+
const maybeRaw = {};
36+
if (JSON.isRawJSON(maybeRaw)) {
37+
const text = maybeRaw.rawJSON;
38+
}
39+
// JSON.parse with reviver context
40+
JSON.parse('{"key":123}', (key, value, context) => {
41+
const src = context.source;
42+
return value;
43+
});
44+
// Existing JSON.parse overloads still work
45+
JSON.parse("{}");
46+
JSON.parse('{"a":1}', (key, value) => value);
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//// [tests/cases/compiler/jsonParseWithSource.ts] ////
2+
3+
=== jsonParseWithSource.ts ===
4+
// JSON.rawJSON
5+
const raw = JSON.rawJSON("123");
6+
>raw : Symbol(raw, Decl(jsonParseWithSource.ts, 1, 5))
7+
>JSON.rawJSON : Symbol(JSON.rawJSON, Decl(lib.esnext.json.d.ts, --, --))
8+
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
9+
>rawJSON : Symbol(JSON.rawJSON, Decl(lib.esnext.json.d.ts, --, --))
10+
11+
const rawStr: string = raw.rawJSON;
12+
>rawStr : Symbol(rawStr, Decl(jsonParseWithSource.ts, 2, 5))
13+
>raw.rawJSON : Symbol(RawJSON.rawJSON, Decl(lib.esnext.json.d.ts, --, --))
14+
>raw : Symbol(raw, Decl(jsonParseWithSource.ts, 1, 5))
15+
>rawJSON : Symbol(RawJSON.rawJSON, Decl(lib.esnext.json.d.ts, --, --))
16+
17+
JSON.stringify({ value: raw });
18+
>JSON.stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
19+
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
20+
>stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
21+
>value : Symbol(value, Decl(jsonParseWithSource.ts, 3, 16))
22+
>raw : Symbol(raw, Decl(jsonParseWithSource.ts, 1, 5))
23+
24+
JSON.stringify({ n: JSON.rawJSON("12345678901234567890") });
25+
>JSON.stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
26+
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
27+
>stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
28+
>n : Symbol(n, Decl(jsonParseWithSource.ts, 4, 16))
29+
>JSON.rawJSON : Symbol(JSON.rawJSON, Decl(lib.esnext.json.d.ts, --, --))
30+
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
31+
>rawJSON : Symbol(JSON.rawJSON, Decl(lib.esnext.json.d.ts, --, --))
32+
33+
// JSON.isRawJSON
34+
const maybeRaw: unknown = {};
35+
>maybeRaw : Symbol(maybeRaw, Decl(jsonParseWithSource.ts, 7, 5))
36+
37+
if (JSON.isRawJSON(maybeRaw)) {
38+
>JSON.isRawJSON : Symbol(JSON.isRawJSON, Decl(lib.esnext.json.d.ts, --, --))
39+
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
40+
>isRawJSON : Symbol(JSON.isRawJSON, Decl(lib.esnext.json.d.ts, --, --))
41+
>maybeRaw : Symbol(maybeRaw, Decl(jsonParseWithSource.ts, 7, 5))
42+
43+
const text: string = maybeRaw.rawJSON;
44+
>text : Symbol(text, Decl(jsonParseWithSource.ts, 9, 9))
45+
>maybeRaw.rawJSON : Symbol(RawJSON.rawJSON, Decl(lib.esnext.json.d.ts, --, --))
46+
>maybeRaw : Symbol(maybeRaw, Decl(jsonParseWithSource.ts, 7, 5))
47+
>rawJSON : Symbol(RawJSON.rawJSON, Decl(lib.esnext.json.d.ts, --, --))
48+
}
49+
50+
// JSON.parse with reviver context
51+
JSON.parse('{"key":123}', (key, value, context) => {
52+
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
53+
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
54+
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
55+
>key : Symbol(key, Decl(jsonParseWithSource.ts, 13, 27))
56+
>value : Symbol(value, Decl(jsonParseWithSource.ts, 13, 31))
57+
>context : Symbol(context, Decl(jsonParseWithSource.ts, 13, 38))
58+
59+
const src: string = context.source;
60+
>src : Symbol(src, Decl(jsonParseWithSource.ts, 14, 9))
61+
>context.source : Symbol(source, Decl(lib.esnext.json.d.ts, --, --))
62+
>context : Symbol(context, Decl(jsonParseWithSource.ts, 13, 38))
63+
>source : Symbol(source, Decl(lib.esnext.json.d.ts, --, --))
64+
65+
return value;
66+
>value : Symbol(value, Decl(jsonParseWithSource.ts, 13, 31))
67+
68+
});
69+
70+
// Existing JSON.parse overloads still work
71+
JSON.parse("{}");
72+
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
73+
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
74+
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
75+
76+
JSON.parse('{"a":1}', (key, value) => value);
77+
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
78+
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
79+
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.esnext.json.d.ts, --, --))
80+
>key : Symbol(key, Decl(jsonParseWithSource.ts, 20, 23))
81+
>value : Symbol(value, Decl(jsonParseWithSource.ts, 20, 27))
82+
>value : Symbol(value, Decl(jsonParseWithSource.ts, 20, 27))
83+
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//// [tests/cases/compiler/jsonParseWithSource.ts] ////
2+
3+
=== jsonParseWithSource.ts ===
4+
// JSON.rawJSON
5+
const raw = JSON.rawJSON("123");
6+
>raw : RawJSON
7+
> : ^^^^^^^
8+
>JSON.rawJSON("123") : RawJSON
9+
> : ^^^^^^^
10+
>JSON.rawJSON : (text: string) => RawJSON
11+
> : ^ ^^ ^^^^^
12+
>JSON : JSON
13+
> : ^^^^
14+
>rawJSON : (text: string) => RawJSON
15+
> : ^ ^^ ^^^^^
16+
>"123" : "123"
17+
> : ^^^^^
18+
19+
const rawStr: string = raw.rawJSON;
20+
>rawStr : string
21+
> : ^^^^^^
22+
>raw.rawJSON : string
23+
> : ^^^^^^
24+
>raw : RawJSON
25+
> : ^^^^^^^
26+
>rawJSON : string
27+
> : ^^^^^^
28+
29+
JSON.stringify({ value: raw });
30+
>JSON.stringify({ value: raw }) : string
31+
> : ^^^^^^
32+
>JSON.stringify : { (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[] | null, space?: string | number): string; }
33+
> : ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^
34+
>JSON : JSON
35+
> : ^^^^
36+
>stringify : { (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[] | null, space?: string | number): string; }
37+
> : ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^
38+
>{ value: raw } : { value: RawJSON; }
39+
> : ^^^^^^^^^^^^^^^^^^^
40+
>value : RawJSON
41+
> : ^^^^^^^
42+
>raw : RawJSON
43+
> : ^^^^^^^
44+
45+
JSON.stringify({ n: JSON.rawJSON("12345678901234567890") });
46+
>JSON.stringify({ n: JSON.rawJSON("12345678901234567890") }) : string
47+
> : ^^^^^^
48+
>JSON.stringify : { (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[] | null, space?: string | number): string; }
49+
> : ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^
50+
>JSON : JSON
51+
> : ^^^^
52+
>stringify : { (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[] | null, space?: string | number): string; }
53+
> : ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^
54+
>{ n: JSON.rawJSON("12345678901234567890") } : { n: RawJSON; }
55+
> : ^^^^^^^^^^^^^^^
56+
>n : RawJSON
57+
> : ^^^^^^^
58+
>JSON.rawJSON("12345678901234567890") : RawJSON
59+
> : ^^^^^^^
60+
>JSON.rawJSON : (text: string) => RawJSON
61+
> : ^ ^^ ^^^^^
62+
>JSON : JSON
63+
> : ^^^^
64+
>rawJSON : (text: string) => RawJSON
65+
> : ^ ^^ ^^^^^
66+
>"12345678901234567890" : "12345678901234567890"
67+
> : ^^^^^^^^^^^^^^^^^^^^^^
68+
69+
// JSON.isRawJSON
70+
const maybeRaw: unknown = {};
71+
>maybeRaw : unknown
72+
> : ^^^^^^^
73+
>{} : {}
74+
> : ^^
75+
76+
if (JSON.isRawJSON(maybeRaw)) {
77+
>JSON.isRawJSON(maybeRaw) : boolean
78+
> : ^^^^^^^
79+
>JSON.isRawJSON : (value: unknown) => value is RawJSON
80+
> : ^ ^^ ^^^^^
81+
>JSON : JSON
82+
> : ^^^^
83+
>isRawJSON : (value: unknown) => value is RawJSON
84+
> : ^ ^^ ^^^^^
85+
>maybeRaw : unknown
86+
> : ^^^^^^^
87+
88+
const text: string = maybeRaw.rawJSON;
89+
>text : string
90+
> : ^^^^^^
91+
>maybeRaw.rawJSON : string
92+
> : ^^^^^^
93+
>maybeRaw : RawJSON
94+
> : ^^^^^^^
95+
>rawJSON : string
96+
> : ^^^^^^
97+
}
98+
99+
// JSON.parse with reviver context
100+
JSON.parse('{"key":123}', (key, value, context) => {
101+
>JSON.parse('{"key":123}', (key, value, context) => { const src: string = context.source; return value;}) : any
102+
>JSON.parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
103+
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
104+
>JSON : JSON
105+
> : ^^^^
106+
>parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
107+
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
108+
>'{"key":123}' : "{\"key\":123}"
109+
> : ^^^^^^^^^^^^^^^
110+
>(key, value, context) => { const src: string = context.source; return value;} : (this: any, key: string, value: any, context: { source: string; }) => any
111+
> : ^ ^^ ^^ ^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^
112+
>key : string
113+
> : ^^^^^^
114+
>value : any
115+
>context : { source: string; }
116+
> : ^^^^^^^^^^ ^^^
117+
118+
const src: string = context.source;
119+
>src : string
120+
> : ^^^^^^
121+
>context.source : string
122+
> : ^^^^^^
123+
>context : { source: string; }
124+
> : ^^^^^^^^^^ ^^^
125+
>source : string
126+
> : ^^^^^^
127+
128+
return value;
129+
>value : any
130+
131+
});
132+
133+
// Existing JSON.parse overloads still work
134+
JSON.parse("{}");
135+
>JSON.parse("{}") : any
136+
>JSON.parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
137+
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
138+
>JSON : JSON
139+
> : ^^^^
140+
>parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
141+
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
142+
>"{}" : "{}"
143+
> : ^^^^
144+
145+
JSON.parse('{"a":1}', (key, value) => value);
146+
>JSON.parse('{"a":1}', (key, value) => value) : any
147+
>JSON.parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
148+
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
149+
>JSON : JSON
150+
> : ^^^^
151+
>parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
152+
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
153+
>'{"a":1}' : "{\"a\":1}"
154+
> : ^^^^^^^^^^^
155+
>(key, value) => value : (this: any, key: string, value: any) => any
156+
> : ^ ^^ ^^ ^^^^^^^^^^ ^^^^^^^^^^^^^
157+
>key : string
158+
> : ^^^^^^
159+
>value : any
160+
>value : any
161+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// @lib: esnext
2+
// @strictNullChecks: true
3+
4+
// JSON.rawJSON
5+
const raw = JSON.rawJSON("123");
6+
const rawStr: string = raw.rawJSON;
7+
JSON.stringify({ value: raw });
8+
JSON.stringify({ n: JSON.rawJSON("12345678901234567890") });
9+
10+
// JSON.isRawJSON
11+
const maybeRaw: unknown = {};
12+
if (JSON.isRawJSON(maybeRaw)) {
13+
const text: string = maybeRaw.rawJSON;
14+
}
15+
16+
// JSON.parse with reviver context
17+
JSON.parse('{"key":123}', (key, value, context) => {
18+
const src: string = context.source;
19+
return value;
20+
});
21+
22+
// Existing JSON.parse overloads still work
23+
JSON.parse("{}");
24+
JSON.parse('{"a":1}', (key, value) => value);

0 commit comments

Comments
 (0)