ts-data-forge 14.11.0 → 14.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,30 +32,73 @@ export declare namespace Json {
32
32
  * }
33
33
  *
34
34
  * assert.isTrue(Result.isErr(failed));
35
- *
36
- * // With reviver
37
- * const jsonWithDate = '{"created": "2024-01-01T00:00:00.000Z"}';
38
- *
39
- * const withReviver = Json.parse(jsonWithDate, (key, value) =>
40
- * key === 'created' && typeof value === 'string' ? new Date(value) : value,
41
- * );
42
- *
43
- * assert.isTrue(Result.isOk(withReviver));
44
35
  * ```
45
36
  *
46
37
  * @param text - A valid JSON string to parse. Can contain any valid JSON data
47
38
  * type: primitives (string, number, boolean, null), arrays, or objects.
48
- * @param reviver - Optional function that transforms parsed values. Called
49
- * for each key-value pair in the JSON. The function receives the key name
50
- * and parsed value, and should return the transformed value. For nested
51
- * objects, inner objects are processed before outer objects.
52
39
  * @returns A `Result<JsonValue, string>` containing:
53
40
  *
54
- * - On success: `Result.ok(parsedValue)` where `parsedValue` is the parsed JSON
41
+ * - On success: `Result.ok(parsedValue)` where `parsedValue` is the parsed
42
+ * JSON.
55
43
  * - On failure: `Result.err(errorMessage)` where `errorMessage` describes the
56
44
  * parsing error
45
+ *
46
+ * The form that takes a reviver is deprecated: its result is whatever the
47
+ * reviver returned, which need not be JSON although it is typed
48
+ * `JsonValue`. Use {@link parseWithReviver}, which types it `unknown`.
49
+ */
50
+ export const parse: Parse;
51
+ /**
52
+ * Safely converts a JSON string into a JavaScript value, passing each value
53
+ * through `reviver` as `JSON.parse` does, without throwing exceptions.
54
+ *
55
+ * The result is whatever the reviver returned for the root, built from what
56
+ * it returned for every child, so it is typed `unknown`: narrow it before
57
+ * use.
58
+ *
59
+ * @example
60
+ *
61
+ * ```ts
62
+ * const jsonWithDate = '{"created": "2024-01-01T00:00:00.000Z"}';
63
+ *
64
+ * const revived = Json.parseWithReviver(jsonWithDate, (key, value) =>
65
+ * key === 'created' && typeof value === 'string' ? new Date(value) : value,
66
+ * );
67
+ *
68
+ * assert.isTrue(Result.isOk(revived));
69
+ *
70
+ * // The result is whatever the reviver returned, typed `unknown`: here
71
+ * // `created` is a `Date`, not JSON.
72
+ * const root = Result.unwrapOkOr(revived, undefined);
73
+ *
74
+ * assert.isTrue(isRecord(root));
75
+ *
76
+ * assert.instanceOf(root['created'], Date);
77
+ * ```
78
+ *
79
+ * @param text - A valid JSON string to parse.
80
+ * @param reviver - Called for each key-value pair, children before their
81
+ * parent, so the `value` a parent is passed holds what the reviver already
82
+ * returned for its children, which need not be JSON. Returning `undefined`
83
+ * for a child deletes that property from an object and leaves a hole in an
84
+ * array. The root is passed with the key `''`. `undefined` parses as
85
+ * {@link parse} does, typed `unknown`.
86
+ * @returns `Result.ok` of what the reviver returned for the root, or
87
+ * `Result.err` of a message describing the parsing error.
57
88
  */
58
- const parse: (text: string, reviver?: (this: unknown, key: string, value: JsonValue) => unknown) => Result<JsonValue, string>;
89
+ export const parseWithReviver: (text: string, reviver: Reviver | undefined) => Result<unknown, string>;
90
+ /** What {@link parseWithReviver} calls for each key-value pair. */
91
+ export type Reviver = (this: unknown, key: string, value: unknown) => unknown;
92
+ /** The call signatures of {@link parse}. */
93
+ type Parse = {
94
+ (text: string): Result<JsonValue, string>;
95
+ /**
96
+ * @deprecated Use `Json.parseWithReviver`, whose result is typed
97
+ * `unknown`: a reviver need not return JSON. The next major removes this
98
+ * form (`NEXT_MAJOR.md`).
99
+ */
100
+ (text: string, reviver?: (this: unknown, key: string, value: JsonValue) => unknown): Result<JsonValue, string>;
101
+ };
59
102
  /**
60
103
  * Safely converts a JavaScript value to a JSON string without throwing
61
104
  * exceptions.
@@ -121,7 +164,7 @@ export declare namespace Json {
121
164
  * - On failure: `Result.err(errorMessage)` where `errorMessage` describes the
122
165
  * error
123
166
  */
124
- const stringify: (value: unknown, replacer?: (this: unknown, key: string, val: unknown) => unknown, space?: UintRangeInclusive<1, 10> | string) => Result<string | undefined, string>;
167
+ export const stringify: (value: unknown, replacer?: (this: unknown, key: string, val: unknown) => unknown, space?: UintRangeInclusive<1, 10> | string) => Result<string | undefined, string>;
125
168
  /**
126
169
  * Safely converts a JavaScript value to a JSON string, including only the
127
170
  * specified properties.
@@ -187,7 +230,7 @@ export declare namespace Json {
187
230
  * - On success: `Result.ok(jsonString)` with only selected properties
188
231
  * - On failure: `Result.err(errorMessage)` describing the serialization error
189
232
  */
190
- const stringifySelected: (value: unknown, propertiesToBeSelected?: readonly (number | string)[], space?: UintRangeInclusive<1, 10> | string) => Result<string | undefined, string>;
233
+ export const stringifySelected: (value: unknown, propertiesToBeSelected?: readonly (number | string)[], space?: UintRangeInclusive<1, 10> | string) => Result<string | undefined, string>;
191
234
  /**
192
235
  * Safely converts a JavaScript record to a JSON string with keys sorted
193
236
  * alphabetically at all levels.
@@ -265,6 +308,7 @@ export declare namespace Json {
265
308
  * alphabetically
266
309
  * - On failure: `Result.err(errorMessage)` describing the serialization error
267
310
  */
268
- const stringifySortedKey: (value: UnknownRecord, space?: UintRangeInclusive<1, 10> | string) => Result<string | undefined, string>;
311
+ export const stringifySortedKey: (value: UnknownRecord, space?: UintRangeInclusive<1, 10> | string) => Result<string | undefined, string>;
312
+ export {};
269
313
  }
270
314
  //# sourceMappingURL=json.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.mts","sourceRoot":"","sources":["../../src/json/json.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,kBAAkB,EAClB,aAAa,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAIjD;;;;GAIG;AACH,yBAAiB,IAAI,CAAC;IACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;IACI,MAAM,KAAK,SACV,MAAM,YACF,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,OAAO,KAClE,MAAM,CAAC,SAAS,EAAE,MAAM,CAc1B,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgEG;IACI,MAAM,SAAS,UACb,OAAO,aACH,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,UACxD,kBAAkB,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,KACzC,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,CAQnC,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgEG;IACI,MAAM,iBAAiB,UACrB,OAAO,2BACW,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,UAC7C,kBAAkB,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,KACzC,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,CAUnC,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4EG;IACI,MAAM,kBAAkB,UACtB,aAAa,UACZ,kBAAkB,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,KACzC,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,CAoBnC,CAAC;CACH"}
1
+ {"version":3,"file":"json.d.mts","sourceRoot":"","sources":["../../src/json/json.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,kBAAkB,EAClB,aAAa,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAIjD;;;;GAIG;AACH,yBAAiB,IAAI,CAAC;IACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACH,MAAM,CAAC,MAAM,KAAK,EAAE,KAQjB,CAAC;IAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,MAAM,CAAC,MAAM,gBAAgB,SACrB,MAAM,WACH,OAAO,GAAG,SAAS,KAC3B,MAAM,CAAC,OAAO,EAAE,MAAM,CAQxB,CAAC;IAEF,mEAAmE;IACnE,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAE9E,4CAA4C;IAI5C,KAAK,KAAK,GAAG;QACX,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAE1C;;;;WAIG;QACH,CACE,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,OAAO,GAClE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;KAC9B,CAAC;IAGF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgEG;IACH,MAAM,CAAC,MAAM,SAAS,UACb,OAAO,aACH,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,UACxD,kBAAkB,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,KACzC,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,CAQnC,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgEG;IACH,MAAM,CAAC,MAAM,iBAAiB,UACrB,OAAO,2BACW,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,UAC7C,kBAAkB,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,KACzC,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,CAUnC,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4EG;IACH,MAAM,CAAC,MAAM,kBAAkB,UACtB,aAAa,UACZ,kBAAkB,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,KACzC,MAAM,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,CAoBnC,CAAC;;CACH"}
@@ -49,6 +49,24 @@ export var Json;
49
49
 
50
50
 
51
51
 
52
+ Json.parse = (text, reviver) =>
53
+
54
+ Json.parseWithReviver(text, reviver);
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
52
70
 
53
71
 
54
72
 
@@ -57,11 +75,24 @@ export var Json;
57
75
 
58
76
 
59
77
 
60
- Json.parse = (text, reviver) => {
61
- try {
62
- return Result.ok(JSON.parse(text,
63
78
 
64
- reviver));
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+ Json.parseWithReviver = (text, reviver) => {
94
+ try {
95
+ return Result.ok(JSON.parse(text, reviver));
65
96
  }
66
97
  catch (error) {
67
98
  const errStr = unknownToString(error);
@@ -131,6 +162,7 @@ export var Json;
131
162
 
132
163
 
133
164
 
165
+
134
166
 
135
167
 
136
168
  Json.stringify = (value, replacer, space) => {
@@ -1 +1 @@
1
- {"version":3,"file":"json.mjs","sourceRoot":"","sources":["../../src/json/json.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"json.mjs","sourceRoot":"","sources":["../../src/json/json.mts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEnE;;;;GAIG;AACH,MAAM,KAAW,IAAI,CAkYpB;AAlYD,WAAiB,IAAI;IACnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACU,UAAK,GAAU,CAC1B,IAAY,EACZ,OAAmE,EACxC,EAAE;IAC7B,iLAAiL;IACjL,qBAAgB,CAAC,IAAI,EAAE,OAA8B,CAGpD,CAAC;IAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACU,qBAAgB,GAAG,CAC9B,IAAY,EACZ,OAA4B,EACH,EAAE;QAC3B,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;YAEtC,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC;IAsBF,yDAAyD;IAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgEG;IACU,cAAS,GAAG,CACvB,KAAc,EACd,QAAgE,EAChE,KAA0C,EACN,EAAE;QACtC,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;YAEtC,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgEG;IACU,sBAAiB,GAAG,CAC/B,KAAc,EACd,sBAAqD,EACrD,KAA0C,EACN,EAAE;QACtC,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,EAAE,CACd,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,sBAAsB,CAAC,EAAE,KAAK,CAAC,CAClE,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;YAEtC,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4EG;IACU,uBAAkB,GAAG,CAChC,KAAoB,EACpB,KAA0C,EACN,EAAE;QACtC,yEAAyE;QACzE,qEAAqE;QACrE,yEAAyE;QACzE,wEAAwE;QACxE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;QAEnC,MAAM,SAAS,GAAG,cAAS,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;YAC7C,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC/B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;YACH,CAAC;YAED,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;YAC5B,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,sBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC;IACvE,CAAC,CAAC;AACJ,CAAC,EAlYgB,IAAI,KAAJ,IAAI,QAkYpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-data-forge",
3
- "version": "14.11.0",
3
+ "version": "14.12.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -49,7 +49,7 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "ts-std-forge": "^0.5.3",
52
- "ts-type-forge": "~9.2.2"
52
+ "ts-type-forge": "~9.2.3"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@emotion/react": "11.14.0",
@@ -59,16 +59,16 @@
59
59
  "@types/react": "19.3.0",
60
60
  "@vitest/browser-playwright": "5.0.5",
61
61
  "eslint": "10.13.0",
62
- "eslint-config-typed": "7.4.0",
63
- "eslint-plugin-ts-fortress": "1.3.13",
64
- "eslint-plugin-ts-type-forge": "0.8.18",
62
+ "eslint-config-typed": "7.6.1",
63
+ "eslint-plugin-ts-fortress": "1.3.16",
64
+ "eslint-plugin-ts-type-forge": "0.8.21",
65
65
  "immer": "11.2.7",
66
66
  "playwright": "1.64.1",
67
67
  "prettier": "3.9.10",
68
68
  "prettier-plugin-organize-imports": "4.3.0",
69
69
  "prettier-plugin-packagejson": "3.0.2",
70
70
  "react": "19.3.1",
71
- "ts-repo-utils": "10.7.2",
71
+ "ts-repo-utils": "10.7.4",
72
72
  "typedoc": "0.28.20",
73
73
  "typedoc-github-theme": "0.4.0",
74
74
  "vite": "8.3.5"
@@ -1107,13 +1107,56 @@ describe('Arr transformations', () => {
1107
1107
 
1108
1108
  const zipped = zip(xs, ys);
1109
1109
 
1110
- expectType<typeof zipped, readonly [readonly [1, number]]>('=');
1110
+ // `ys` may be shorter than `xs`, so the pair is optional.
1111
+ expectType<typeof zipped, readonly [(readonly [1, number])?]>('=');
1111
1112
 
1112
1113
  test('case 3', () => {
1113
1114
  assert.deepStrictEqual(zipped, [[1, 4]]);
1114
1115
  });
1115
1116
  }
1116
1117
 
1118
+ {
1119
+ const xs = [1, 2] as const;
1120
+
1121
+ const ys: readonly number[] = [] as const;
1122
+
1123
+ const zipped = zip(xs, ys);
1124
+
1125
+ expectType<
1126
+ typeof zipped,
1127
+ readonly [(readonly [1, number])?, (readonly [2, number])?]
1128
+ >('=');
1129
+
1130
+ test('a tuple zipped with an empty plain array is empty', () => {
1131
+ assert.deepStrictEqual(zipped, []);
1132
+
1133
+ expectType<(typeof zipped)[0], readonly [1, number] | undefined>('=');
1134
+
1135
+ assert.isUndefined(zipped[0]);
1136
+ });
1137
+ }
1138
+
1139
+ {
1140
+ const xs: readonly number[] = [] as const;
1141
+
1142
+ const ys = [1, 2] as const;
1143
+
1144
+ const zipped = zip(xs, ys);
1145
+
1146
+ expectType<
1147
+ typeof zipped,
1148
+ readonly [(readonly [number, 1])?, (readonly [number, 2])?]
1149
+ >('=');
1150
+
1151
+ test('an empty plain array zipped with a tuple is empty', () => {
1152
+ assert.deepStrictEqual(zipped, []);
1153
+
1154
+ expectType<(typeof zipped)[0], readonly [number, 1] | undefined>('=');
1155
+
1156
+ assert.isUndefined(zipped[0]);
1157
+ });
1158
+ }
1159
+
1117
1160
  // testArrayEquality({
1118
1161
  // testName: 'zip',
1119
1162
  // target: zip([0, 1, 2, 3, 4], [5, 6, 7, 8, 9]),
package/src/json/json.mts CHANGED
@@ -39,41 +39,75 @@ export namespace Json {
39
39
  * }
40
40
  *
41
41
  * assert.isTrue(Result.isErr(failed));
42
- *
43
- * // With reviver
44
- * const jsonWithDate = '{"created": "2024-01-01T00:00:00.000Z"}';
45
- *
46
- * const withReviver = Json.parse(jsonWithDate, (key, value) =>
47
- * key === 'created' && typeof value === 'string' ? new Date(value) : value,
48
- * );
49
- *
50
- * assert.isTrue(Result.isOk(withReviver));
51
42
  * ```
52
43
  *
53
44
  * @param text - A valid JSON string to parse. Can contain any valid JSON data
54
45
  * type: primitives (string, number, boolean, null), arrays, or objects.
55
- * @param reviver - Optional function that transforms parsed values. Called
56
- * for each key-value pair in the JSON. The function receives the key name
57
- * and parsed value, and should return the transformed value. For nested
58
- * objects, inner objects are processed before outer objects.
59
46
  * @returns A `Result<JsonValue, string>` containing:
60
47
  *
61
- * - On success: `Result.ok(parsedValue)` where `parsedValue` is the parsed JSON
48
+ * - On success: `Result.ok(parsedValue)` where `parsedValue` is the parsed
49
+ * JSON.
62
50
  * - On failure: `Result.err(errorMessage)` where `errorMessage` describes the
63
51
  * parsing error
52
+ *
53
+ * The form that takes a reviver is deprecated: its result is whatever the
54
+ * reviver returned, which need not be JSON although it is typed
55
+ * `JsonValue`. Use {@link parseWithReviver}, which types it `unknown`.
64
56
  */
65
- export const parse = (
57
+ export const parse: Parse = (
66
58
  text: string,
67
59
  reviver?: (this: unknown, key: string, value: JsonValue) => unknown,
68
- ): Result<JsonValue, string> => {
60
+ ): Result<JsonValue, string> =>
61
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion -- without a reviver the result is JSON; with one, the deprecated form types it `JsonValue` as it always did
62
+ parseWithReviver(text, reviver as Reviver | undefined) as Result<
63
+ JsonValue,
64
+ string
65
+ >;
66
+
67
+ /**
68
+ * Safely converts a JSON string into a JavaScript value, passing each value
69
+ * through `reviver` as `JSON.parse` does, without throwing exceptions.
70
+ *
71
+ * The result is whatever the reviver returned for the root, built from what
72
+ * it returned for every child, so it is typed `unknown`: narrow it before
73
+ * use.
74
+ *
75
+ * @example
76
+ *
77
+ * ```ts
78
+ * const jsonWithDate = '{"created": "2024-01-01T00:00:00.000Z"}';
79
+ *
80
+ * const revived = Json.parseWithReviver(jsonWithDate, (key, value) =>
81
+ * key === 'created' && typeof value === 'string' ? new Date(value) : value,
82
+ * );
83
+ *
84
+ * assert.isTrue(Result.isOk(revived));
85
+ *
86
+ * // The result is whatever the reviver returned, typed `unknown`: here
87
+ * // `created` is a `Date`, not JSON.
88
+ * const root = Result.unwrapOkOr(revived, undefined);
89
+ *
90
+ * assert.isTrue(isRecord(root));
91
+ *
92
+ * assert.instanceOf(root['created'], Date);
93
+ * ```
94
+ *
95
+ * @param text - A valid JSON string to parse.
96
+ * @param reviver - Called for each key-value pair, children before their
97
+ * parent, so the `value` a parent is passed holds what the reviver already
98
+ * returned for its children, which need not be JSON. Returning `undefined`
99
+ * for a child deletes that property from an object and leaves a hole in an
100
+ * array. The root is passed with the key `''`. `undefined` parses as
101
+ * {@link parse} does, typed `unknown`.
102
+ * @returns `Result.ok` of what the reviver returned for the root, or
103
+ * `Result.err` of a message describing the parsing error.
104
+ */
105
+ export const parseWithReviver = (
106
+ text: string,
107
+ reviver: Reviver | undefined,
108
+ ): Result<unknown, string> => {
69
109
  try {
70
- return Result.ok(
71
- JSON.parse(
72
- text,
73
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
74
- reviver as (this: unknown, key: string, value: unknown) => unknown,
75
- ),
76
- );
110
+ return Result.ok(JSON.parse(text, reviver));
77
111
  } catch (error: unknown) {
78
112
  const errStr = unknownToString(error);
79
113
 
@@ -81,6 +115,28 @@ export namespace Json {
81
115
  }
82
116
  };
83
117
 
118
+ /** What {@link parseWithReviver} calls for each key-value pair. */
119
+ export type Reviver = (this: unknown, key: string, value: unknown) => unknown;
120
+
121
+ /** The call signatures of {@link parse}. */
122
+ // Two signatures rather than one with an optional reviver, so that the one
123
+ // taking a reviver carries `@deprecated` and the other does not.
124
+ /* eslint-disable @typescript-eslint/unified-signatures */
125
+ type Parse = {
126
+ (text: string): Result<JsonValue, string>;
127
+
128
+ /**
129
+ * @deprecated Use `Json.parseWithReviver`, whose result is typed
130
+ * `unknown`: a reviver need not return JSON. The next major removes this
131
+ * form (`NEXT_MAJOR.md`).
132
+ */
133
+ (
134
+ text: string,
135
+ reviver?: (this: unknown, key: string, value: JsonValue) => unknown,
136
+ ): Result<JsonValue, string>;
137
+ };
138
+ /* eslint-enable @typescript-eslint/unified-signatures */
139
+
84
140
  /**
85
141
  * Safely converts a JavaScript value to a JSON string without throwing
86
142
  * exceptions.
@@ -125,7 +125,9 @@ describe('parse', () => {
125
125
 
126
126
  expect(() => Json.parse('undefined')).not.toThrow();
127
127
  });
128
+ });
128
129
 
130
+ describe('parseWithReviver', () => {
129
131
  test('should use reviver function to transform values', () => {
130
132
  const dateReviver = (_key: string, value: unknown): unknown =>
131
133
  typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/u.test(value)
@@ -134,7 +136,7 @@ describe('parse', () => {
134
136
 
135
137
  const jsonString = '{"name":"test","created":"2023-12-01T10:00:00.000Z"}';
136
138
 
137
- const result = Json.parse(jsonString, dateReviver);
139
+ const result = Json.parseWithReviver(jsonString, dateReviver);
138
140
 
139
141
  assert.isTrue(Result.isOk(result));
140
142
 
@@ -157,7 +159,7 @@ describe('parse', () => {
157
159
  ? Result.unwrapOkOr(Num.safeParseInt(value), Number.NaN)
158
160
  : value;
159
161
 
160
- const result = Json.parse(
162
+ const result = Json.parseWithReviver(
161
163
  '{"number":"42","text":"hello"}',
162
164
  transformReviver,
163
165
  );
@@ -180,6 +182,89 @@ describe('parse', () => {
180
182
 
181
183
  expect(result.value.text).toBe('hello');
182
184
  });
185
+
186
+ test('should return what the reviver returns for the root, even if it is not JSON', () => {
187
+ const result = Json.parseWithReviver('{"a":1}', (key, value) =>
188
+ key === '' ? undefined : value,
189
+ );
190
+
191
+ assert.isTrue(Result.isOk(result));
192
+
193
+ expect(result.value).toBeUndefined();
194
+ });
195
+
196
+ test('should delete an object property and leave an array hole where the reviver returns undefined', () => {
197
+ const result = Json.parseWithReviver('{"a":1,"b":[1,2,3]}', (key, value) =>
198
+ key === 'a' || key === '1' ? undefined : value,
199
+ );
200
+
201
+ assert.isTrue(Result.isOk(result));
202
+
203
+ const revived: unknown = result.value;
204
+
205
+ assert.isTrue(isRecord(revived));
206
+
207
+ assert.isFalse(hasKey(revived, 'a'));
208
+
209
+ assert.isTrue(hasKey(revived, 'b'));
210
+
211
+ const arr = revived.b;
212
+
213
+ assert.isTrue(Array.isArray(arr));
214
+
215
+ expect(arr).toHaveLength(3);
216
+
217
+ expect(arr[0]).toBe(1);
218
+
219
+ expect(arr[1]).toBeUndefined();
220
+
221
+ expect(arr[2]).toBe(3);
222
+
223
+ // With the same elements, only a hole at index 1 makes these differ.
224
+ expect(arr).not.toStrictEqual([1, undefined, 3]);
225
+ });
226
+
227
+ test('should pass the reviver the children it has already revived', () => {
228
+ const mut_seen: unknown[] = [];
229
+
230
+ const result = Json.parseWithReviver('{"inner":{"n":1}}', (key, value) => {
231
+ if (key === 'inner') {
232
+ mut_seen.push(structuredClone(value));
233
+
234
+ return 'replaced';
235
+ }
236
+
237
+ if (key === '') {
238
+ mut_seen.push(structuredClone(value));
239
+ }
240
+
241
+ return value;
242
+ });
243
+
244
+ assert.isTrue(Result.isOk(result));
245
+
246
+ assert.deepStrictEqual(mut_seen, [{ n: 1 }, { inner: 'replaced' }]);
247
+ });
248
+
249
+ test('parses as parse does with no reviver', () => {
250
+ assert.deepStrictEqual(
251
+ Json.parseWithReviver('{"a":[1,2]}', undefined),
252
+ Result.ok({ a: [1, 2] }),
253
+ );
254
+
255
+ assert.isTrue(Result.isErr(Json.parseWithReviver('{{{', undefined)));
256
+ });
257
+ });
258
+
259
+ describe('parse with a reviver (deprecated)', () => {
260
+ test('still revives, as parseWithReviver does', () => {
261
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- the form under test
262
+ const result = Json.parse('{"a":1}', (key, value) =>
263
+ key === 'a' ? 2 : value,
264
+ );
265
+
266
+ assert.deepStrictEqual(result, Result.ok({ a: 2 }));
267
+ });
183
268
  });
184
269
 
185
270
  describe('stringify', () => {
@@ -1,5 +1,3 @@
1
- // A `Date` is one of the values compared whole, so the cases build some.
2
- /* eslint-disable unicorn/prefer-temporal */
3
1
  import type { DeepReadonly, ReadonlyRecord } from 'ts-type-forge';
4
2
  import { expectType } from '../expect-type.mjs';
5
3
  import { hasKey, isRecord } from '../guard/index.mjs';