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.
- package/dist/json/json.d.mts +62 -18
- package/dist/json/json.d.mts.map +1 -1
- package/dist/json/json.mjs +36 -4
- package/dist/json/json.mjs.map +1 -1
- package/package.json +6 -6
- package/src/array/impl/array-utils-transformation.test.mts +44 -1
- package/src/json/json.mts +79 -23
- package/src/json/json.test.mts +87 -2
- package/src/others/replace-equal-deep.test.mts +0 -2
package/dist/json/json.d.mts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
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,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
|
-
|
|
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) => {
|
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",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"ts-std-forge": "^0.5.3",
|
|
52
|
-
"ts-type-forge": "~9.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.
|
|
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",
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
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
|
);
|
|
@@ -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';
|