ts-data-forge 14.11.1 → 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.
- package/dist/json/json.d.mts +59 -33
- package/dist/json/json.d.mts.map +1 -1
- package/dist/json/json.mjs +18 -4
- package/dist/json/json.mjs.map +1 -1
- package/package.json +4 -4
- package/src/json/json.mts +77 -39
- package/src/json/json.test.mts +27 -5
package/dist/json/json.d.mts
CHANGED
|
@@ -32,48 +32,73 @@ export declare namespace Json {
|
|
|
32
32
|
* }
|
|
33
33
|
*
|
|
34
34
|
* assert.isTrue(Result.isErr(failed));
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @param text - A valid JSON string to parse. Can contain any valid JSON data
|
|
38
|
+
* type: primitives (string, number, boolean, null), arrays, or objects.
|
|
39
|
+
* @returns A `Result<JsonValue, string>` containing:
|
|
40
|
+
*
|
|
41
|
+
* - On success: `Result.ok(parsedValue)` where `parsedValue` is the parsed
|
|
42
|
+
* JSON.
|
|
43
|
+
* - On failure: `Result.err(errorMessage)` where `errorMessage` describes the
|
|
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.
|
|
35
54
|
*
|
|
36
|
-
*
|
|
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
|
|
37
62
|
* const jsonWithDate = '{"created": "2024-01-01T00:00:00.000Z"}';
|
|
38
63
|
*
|
|
39
|
-
* const
|
|
64
|
+
* const revived = Json.parseWithReviver(jsonWithDate, (key, value) =>
|
|
40
65
|
* key === 'created' && typeof value === 'string' ? new Date(value) : value,
|
|
41
66
|
* );
|
|
42
67
|
*
|
|
43
|
-
* assert.isTrue(Result.isOk(
|
|
68
|
+
* assert.isTrue(Result.isOk(revived));
|
|
44
69
|
*
|
|
45
|
-
* //
|
|
46
|
-
* // `created` is a `Date
|
|
47
|
-
*
|
|
48
|
-
* const revived: unknown = withReviver.value;
|
|
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);
|
|
49
73
|
*
|
|
50
|
-
* assert.isTrue(isRecord(
|
|
74
|
+
* assert.isTrue(isRecord(root));
|
|
51
75
|
*
|
|
52
|
-
* assert.instanceOf(
|
|
76
|
+
* assert.instanceOf(root['created'], Date);
|
|
53
77
|
* ```
|
|
54
78
|
*
|
|
55
|
-
* @param text - A valid JSON string to parse.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* for
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* `undefined` for a child deletes that property from an object and leaves
|
|
65
|
-
* a hole in an array. The root is passed with the key `''`.
|
|
66
|
-
* @returns A `Result<JsonValue, string>` containing:
|
|
67
|
-
*
|
|
68
|
-
* - On success: `Result.ok(parsedValue)` where `parsedValue` is the parsed
|
|
69
|
-
* JSON. With a reviver it is whatever the reviver returned for the root
|
|
70
|
-
* (a `Date`, `undefined`, ...), which is not checked to be JSON although
|
|
71
|
-
* it is typed `JsonValue`; widen it to `unknown` before narrowing it,
|
|
72
|
-
* as the example does.
|
|
73
|
-
* - On failure: `Result.err(errorMessage)` where `errorMessage` describes the
|
|
74
|
-
* parsing error
|
|
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.
|
|
75
88
|
*/
|
|
76
|
-
const
|
|
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
|
+
};
|
|
77
102
|
/**
|
|
78
103
|
* Safely converts a JavaScript value to a JSON string without throwing
|
|
79
104
|
* exceptions.
|
|
@@ -139,7 +164,7 @@ export declare namespace Json {
|
|
|
139
164
|
* - On failure: `Result.err(errorMessage)` where `errorMessage` describes the
|
|
140
165
|
* error
|
|
141
166
|
*/
|
|
142
|
-
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>;
|
|
143
168
|
/**
|
|
144
169
|
* Safely converts a JavaScript value to a JSON string, including only the
|
|
145
170
|
* specified properties.
|
|
@@ -205,7 +230,7 @@ export declare namespace Json {
|
|
|
205
230
|
* - On success: `Result.ok(jsonString)` with only selected properties
|
|
206
231
|
* - On failure: `Result.err(errorMessage)` describing the serialization error
|
|
207
232
|
*/
|
|
208
|
-
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>;
|
|
209
234
|
/**
|
|
210
235
|
* Safely converts a JavaScript record to a JSON string with keys sorted
|
|
211
236
|
* alphabetically at all levels.
|
|
@@ -283,6 +308,7 @@ export declare namespace Json {
|
|
|
283
308
|
* alphabetically
|
|
284
309
|
* - On failure: `Result.err(errorMessage)` describing the serialization error
|
|
285
310
|
*/
|
|
286
|
-
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 {};
|
|
287
313
|
}
|
|
288
314
|
//# sourceMappingURL=json.d.mts.map
|
package/dist/json/json.d.mts.map
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/json/json.mjs
CHANGED
|
@@ -49,6 +49,15 @@ 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
|
+
|
|
52
61
|
|
|
53
62
|
|
|
54
63
|
|
|
@@ -75,11 +84,15 @@ export var Json;
|
|
|
75
84
|
|
|
76
85
|
|
|
77
86
|
|
|
78
|
-
Json.parse = (text, reviver) => {
|
|
79
|
-
try {
|
|
80
|
-
return Result.ok(JSON.parse(text,
|
|
81
87
|
|
|
82
|
-
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
Json.parseWithReviver = (text, reviver) => {
|
|
94
|
+
try {
|
|
95
|
+
return Result.ok(JSON.parse(text, reviver));
|
|
83
96
|
}
|
|
84
97
|
catch (error) {
|
|
85
98
|
const errStr = unknownToString(error);
|
|
@@ -149,6 +162,7 @@ export var Json;
|
|
|
149
162
|
|
|
150
163
|
|
|
151
164
|
|
|
165
|
+
|
|
152
166
|
|
|
153
167
|
|
|
154
168
|
Json.stringify = (value, replacer, space) => {
|
package/dist/json/json.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"json.mjs","sourceRoot":"","sources":["../../src/json/json.mts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-data-forge",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.12.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -59,9 +59,9 @@
|
|
|
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.6.
|
|
63
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
64
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
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",
|
package/src/json/json.mts
CHANGED
|
@@ -39,59 +39,75 @@ export namespace Json {
|
|
|
39
39
|
* }
|
|
40
40
|
*
|
|
41
41
|
* assert.isTrue(Result.isErr(failed));
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param text - A valid JSON string to parse. Can contain any valid JSON data
|
|
45
|
+
* type: primitives (string, number, boolean, null), arrays, or objects.
|
|
46
|
+
* @returns A `Result<JsonValue, string>` containing:
|
|
47
|
+
*
|
|
48
|
+
* - On success: `Result.ok(parsedValue)` where `parsedValue` is the parsed
|
|
49
|
+
* JSON.
|
|
50
|
+
* - On failure: `Result.err(errorMessage)` where `errorMessage` describes the
|
|
51
|
+
* parsing error
|
|
42
52
|
*
|
|
43
|
-
*
|
|
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`.
|
|
56
|
+
*/
|
|
57
|
+
export const parse: Parse = (
|
|
58
|
+
text: string,
|
|
59
|
+
reviver?: (this: unknown, key: string, value: JsonValue) => unknown,
|
|
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
|
|
44
78
|
* const jsonWithDate = '{"created": "2024-01-01T00:00:00.000Z"}';
|
|
45
79
|
*
|
|
46
|
-
* const
|
|
80
|
+
* const revived = Json.parseWithReviver(jsonWithDate, (key, value) =>
|
|
47
81
|
* key === 'created' && typeof value === 'string' ? new Date(value) : value,
|
|
48
82
|
* );
|
|
49
83
|
*
|
|
50
|
-
* assert.isTrue(Result.isOk(
|
|
84
|
+
* assert.isTrue(Result.isOk(revived));
|
|
51
85
|
*
|
|
52
|
-
* //
|
|
53
|
-
* // `created` is a `Date
|
|
54
|
-
*
|
|
55
|
-
* const revived: unknown = withReviver.value;
|
|
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);
|
|
56
89
|
*
|
|
57
|
-
* assert.isTrue(isRecord(
|
|
90
|
+
* assert.isTrue(isRecord(root));
|
|
58
91
|
*
|
|
59
|
-
* assert.instanceOf(
|
|
92
|
+
* assert.instanceOf(root['created'], Date);
|
|
60
93
|
* ```
|
|
61
94
|
*
|
|
62
|
-
* @param text - A valid JSON string to parse.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* for
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* `undefined` for a child deletes that property from an object and leaves
|
|
72
|
-
* a hole in an array. The root is passed with the key `''`.
|
|
73
|
-
* @returns A `Result<JsonValue, string>` containing:
|
|
74
|
-
*
|
|
75
|
-
* - On success: `Result.ok(parsedValue)` where `parsedValue` is the parsed
|
|
76
|
-
* JSON. With a reviver it is whatever the reviver returned for the root
|
|
77
|
-
* (a `Date`, `undefined`, ...), which is not checked to be JSON although
|
|
78
|
-
* it is typed `JsonValue`; widen it to `unknown` before narrowing it,
|
|
79
|
-
* as the example does.
|
|
80
|
-
* - On failure: `Result.err(errorMessage)` where `errorMessage` describes the
|
|
81
|
-
* parsing error
|
|
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.
|
|
82
104
|
*/
|
|
83
|
-
export const
|
|
105
|
+
export const parseWithReviver = (
|
|
84
106
|
text: string,
|
|
85
|
-
reviver
|
|
86
|
-
): Result<
|
|
107
|
+
reviver: Reviver | undefined,
|
|
108
|
+
): Result<unknown, string> => {
|
|
87
109
|
try {
|
|
88
|
-
return Result.ok(
|
|
89
|
-
JSON.parse(
|
|
90
|
-
text,
|
|
91
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
92
|
-
reviver as (this: unknown, key: string, value: unknown) => unknown,
|
|
93
|
-
),
|
|
94
|
-
);
|
|
110
|
+
return Result.ok(JSON.parse(text, reviver));
|
|
95
111
|
} catch (error: unknown) {
|
|
96
112
|
const errStr = unknownToString(error);
|
|
97
113
|
|
|
@@ -99,6 +115,28 @@ export namespace Json {
|
|
|
99
115
|
}
|
|
100
116
|
};
|
|
101
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
|
+
|
|
102
140
|
/**
|
|
103
141
|
* Safely converts a JavaScript value to a JSON string without throwing
|
|
104
142
|
* exceptions.
|
package/src/json/json.test.mts
CHANGED
|
@@ -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.
|
|
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.
|
|
162
|
+
const result = Json.parseWithReviver(
|
|
161
163
|
'{"number":"42","text":"hello"}',
|
|
162
164
|
transformReviver,
|
|
163
165
|
);
|
|
@@ -182,7 +184,7 @@ describe('parse', () => {
|
|
|
182
184
|
});
|
|
183
185
|
|
|
184
186
|
test('should return what the reviver returns for the root, even if it is not JSON', () => {
|
|
185
|
-
const result = Json.
|
|
187
|
+
const result = Json.parseWithReviver('{"a":1}', (key, value) =>
|
|
186
188
|
key === '' ? undefined : value,
|
|
187
189
|
);
|
|
188
190
|
|
|
@@ -192,7 +194,7 @@ describe('parse', () => {
|
|
|
192
194
|
});
|
|
193
195
|
|
|
194
196
|
test('should delete an object property and leave an array hole where the reviver returns undefined', () => {
|
|
195
|
-
const result = Json.
|
|
197
|
+
const result = Json.parseWithReviver('{"a":1,"b":[1,2,3]}', (key, value) =>
|
|
196
198
|
key === 'a' || key === '1' ? undefined : value,
|
|
197
199
|
);
|
|
198
200
|
|
|
@@ -225,7 +227,7 @@ describe('parse', () => {
|
|
|
225
227
|
test('should pass the reviver the children it has already revived', () => {
|
|
226
228
|
const mut_seen: unknown[] = [];
|
|
227
229
|
|
|
228
|
-
const result = Json.
|
|
230
|
+
const result = Json.parseWithReviver('{"inner":{"n":1}}', (key, value) => {
|
|
229
231
|
if (key === 'inner') {
|
|
230
232
|
mut_seen.push(structuredClone(value));
|
|
231
233
|
|
|
@@ -243,6 +245,26 @@ describe('parse', () => {
|
|
|
243
245
|
|
|
244
246
|
assert.deepStrictEqual(mut_seen, [{ n: 1 }, { inner: 'replaced' }]);
|
|
245
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
|
+
});
|
|
246
268
|
});
|
|
247
269
|
|
|
248
270
|
describe('stringify', () => {
|