@fgv/ts-json-base 5.0.0-25 → 5.0.0-27
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/README.md +150 -0
- package/dist/ts-json-base.d.ts +20 -2
- package/dist/tsdoc-metadata.json +1 -1
- package/eslint.config.js +16 -0
- package/lib/packlets/json/common.d.ts +18 -2
- package/lib/packlets/json/common.js +7 -3
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -32,3 +32,153 @@ type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
|
32
32
|
interface JsonArray extends Array<JsonValue> { }
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### JsonCompatible Type
|
|
36
|
+
|
|
37
|
+
The `JsonCompatible<T>` type provides compile-time validation that a type is JSON-serializable, unlike `JsonValue` which requires an index signature. This is particularly useful for strongly-typed interfaces.
|
|
38
|
+
|
|
39
|
+
#### Basic Usage
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { JsonCompatible } from '@fgv/ts-json-base';
|
|
43
|
+
|
|
44
|
+
// ✅ JSON-compatible interface
|
|
45
|
+
interface UserData {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
preferences: {
|
|
49
|
+
theme: 'light' | 'dark';
|
|
50
|
+
notifications: boolean;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ✅ This type remains unchanged because it's fully JSON-compatible
|
|
55
|
+
type ValidatedUserData = JsonCompatible<UserData>;
|
|
56
|
+
|
|
57
|
+
// ❌ Interface with non-JSON properties
|
|
58
|
+
interface UserWithMethods {
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
save(): Promise<void>; // Functions are not JSON-serializable
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ❌ This type transforms 'save' to an error type
|
|
65
|
+
type InvalidUserData = JsonCompatible<UserWithMethods>;
|
|
66
|
+
// Result: { id: string; name: string; save: ['Error: Function is not JSON-compatible'] }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### The MapOf Pattern
|
|
70
|
+
|
|
71
|
+
The most powerful application is using `JsonCompatible<T>` as a constraint with default type parameters:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
class MapOf<T, TV extends JsonCompatible<T> = JsonCompatible<T>> extends Map<string, TV> {
|
|
75
|
+
public override set(key: string, value: TV): this {
|
|
76
|
+
return super.set(key, value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ✅ Works perfectly with JSON-compatible types
|
|
81
|
+
const userMap = new MapOf<UserData>();
|
|
82
|
+
userMap.set('user1', {
|
|
83
|
+
id: 'user1',
|
|
84
|
+
name: 'Alice',
|
|
85
|
+
preferences: { theme: 'dark', notifications: true }
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ❌ Prevents usage with non-JSON-compatible types
|
|
89
|
+
const badMap = new MapOf<UserWithMethods>();
|
|
90
|
+
// The 'save' method becomes type 'never', making the interface unusable
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Real-World Examples
|
|
94
|
+
|
|
95
|
+
**API Response Caching:**
|
|
96
|
+
```typescript
|
|
97
|
+
interface ApiResponse {
|
|
98
|
+
id: string;
|
|
99
|
+
data: unknown;
|
|
100
|
+
timestamp: number;
|
|
101
|
+
metadata: Record<string, string | number>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class ApiCache extends MapOf<ApiResponse> {
|
|
105
|
+
setWithTTL(key: string, response: ApiResponse, ttlMs: number) {
|
|
106
|
+
this.set(key, response);
|
|
107
|
+
setTimeout(() => this.delete(key), ttlMs);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Configuration Management:**
|
|
113
|
+
```typescript
|
|
114
|
+
interface AppConfig {
|
|
115
|
+
features: Record<string, boolean>;
|
|
116
|
+
limits: {
|
|
117
|
+
maxUsers: number;
|
|
118
|
+
maxStorage: number;
|
|
119
|
+
};
|
|
120
|
+
ui: {
|
|
121
|
+
theme: 'light' | 'dark';
|
|
122
|
+
language: string;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Ensures config is always serializable for storage/transmission
|
|
127
|
+
const configStore = new MapOf<AppConfig>();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Data Transfer Objects:**
|
|
131
|
+
```typescript
|
|
132
|
+
// ✅ Perfect for DTOs - no methods, just data
|
|
133
|
+
interface CreateUserRequest {
|
|
134
|
+
name: string;
|
|
135
|
+
email: string;
|
|
136
|
+
preferences?: {
|
|
137
|
+
newsletter: boolean;
|
|
138
|
+
theme: string;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const requestCache = new MapOf<CreateUserRequest>();
|
|
143
|
+
|
|
144
|
+
// ❌ Domain objects with behavior are rejected
|
|
145
|
+
interface User extends CreateUserRequest {
|
|
146
|
+
id: string;
|
|
147
|
+
save(): Promise<void>;
|
|
148
|
+
validate(): boolean;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// This won't work - methods become 'never'
|
|
152
|
+
// const userStore = new MapOf<User>();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Array and Nested Validation
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// ✅ Arrays of JSON-compatible types work fine
|
|
159
|
+
interface DataStructure {
|
|
160
|
+
numbers: number[];
|
|
161
|
+
objects: Array<{ id: number; name: string }>;
|
|
162
|
+
matrix: number[][];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const dataMap = new MapOf<DataStructure>();
|
|
166
|
+
|
|
167
|
+
// ❌ Arrays with functions are detected
|
|
168
|
+
interface WithHandlers {
|
|
169
|
+
callbacks: Array<() => void>; // Functions in arrays also become 'never'
|
|
170
|
+
data: string[];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// The 'callbacks' property becomes Array<['Error: Function is not JSON-compatible']>
|
|
174
|
+
const handlersMap = new MapOf<WithHandlers>();
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Benefits
|
|
178
|
+
|
|
179
|
+
1. **Compile-time safety**: Non-serializable types are caught during development
|
|
180
|
+
2. **Clear interfaces**: Makes it obvious which types are meant for serialization
|
|
181
|
+
3. **Architecture enforcement**: Separates data objects from behavior objects
|
|
182
|
+
4. **Runtime protection**: Prevents serialization errors in production
|
|
183
|
+
5. **IDE support**: Full autocomplete and error highlighting
|
|
184
|
+
|
package/dist/ts-json-base.d.ts
CHANGED
|
@@ -153,8 +153,8 @@ export declare function isJsonArray(from: unknown): from is JsonArray;
|
|
|
153
153
|
/**
|
|
154
154
|
* Test if an `unknown` is potentially a {@link JsonObject | JsonObject}.
|
|
155
155
|
* @param from - The `unknown` to be tested.
|
|
156
|
-
* @returns `true` if the supplied parameter is a non-array, non-special object
|
|
157
|
-
* `false` otherwise.
|
|
156
|
+
* @returns `true` if the supplied parameter is a non-array, non-special object
|
|
157
|
+
* with no symbol keys, `false` otherwise.
|
|
158
158
|
* @public
|
|
159
159
|
*/
|
|
160
160
|
export declare function isJsonObject(from: unknown): from is JsonObject;
|
|
@@ -202,6 +202,24 @@ declare const jsonArray: Converter<JsonArray, IJsonConverterContext>;
|
|
|
202
202
|
*/
|
|
203
203
|
declare const jsonArray_2: Validator<JsonArray, IJsonValidatorContext>;
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* A constrained type that is compatible with JSON serialization.
|
|
207
|
+
* @param T - The type to be constrained
|
|
208
|
+
* @returns A constrained type that is compatible with JSON serialization.
|
|
209
|
+
* @public
|
|
210
|
+
*/
|
|
211
|
+
export declare type JsonCompatible<T> = T extends JsonPrimitive ? T : T extends Array<unknown> ? JsonCompatibleArray<T[number]> : T extends Function ? ['Error: Function is not JSON-compatible'] : T extends object ? {
|
|
212
|
+
[K in keyof T]: JsonCompatible<T[K]>;
|
|
213
|
+
} : ['Error: Non-JSON type'];
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A type that represents an array of JSON-compatible values.
|
|
217
|
+
* @param T - The type to be constrained
|
|
218
|
+
* @returns A constrained type that is compatible with JSON serialization.
|
|
219
|
+
* @public
|
|
220
|
+
*/
|
|
221
|
+
export declare type JsonCompatibleArray<T> = Array<JsonCompatible<T>>;
|
|
222
|
+
|
|
205
223
|
declare namespace JsonFile {
|
|
206
224
|
export {
|
|
207
225
|
readJsonFileSync,
|
package/dist/tsdoc-metadata.json
CHANGED
package/eslint.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// ESLint 9 flat config
|
|
2
|
+
const nodeProfile = require('@rushstack/eslint-config/flat/profile/node');
|
|
3
|
+
const packletsPlugin = require('@rushstack/eslint-config/flat/mixins/packlets');
|
|
4
|
+
const tsdocPlugin = require('@rushstack/eslint-config/flat/mixins/tsdoc');
|
|
5
|
+
|
|
6
|
+
module.exports = [
|
|
7
|
+
...nodeProfile,
|
|
8
|
+
packletsPlugin,
|
|
9
|
+
...tsdocPlugin,
|
|
10
|
+
{
|
|
11
|
+
// Override specific rules if needed
|
|
12
|
+
rules: {
|
|
13
|
+
'@rushstack/packlets/mechanics': 'warn'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
];
|
|
@@ -29,6 +29,22 @@ export interface JsonArray extends Array<JsonValue> {
|
|
|
29
29
|
* @public
|
|
30
30
|
*/
|
|
31
31
|
export type JsonValueType = 'primitive' | 'object' | 'array';
|
|
32
|
+
/**
|
|
33
|
+
* A constrained type that is compatible with JSON serialization.
|
|
34
|
+
* @param T - The type to be constrained
|
|
35
|
+
* @returns A constrained type that is compatible with JSON serialization.
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export type JsonCompatible<T> = T extends JsonPrimitive ? T : T extends Array<unknown> ? JsonCompatibleArray<T[number]> : T extends Function ? ['Error: Function is not JSON-compatible'] : T extends object ? {
|
|
39
|
+
[K in keyof T]: JsonCompatible<T[K]>;
|
|
40
|
+
} : ['Error: Non-JSON type'];
|
|
41
|
+
/**
|
|
42
|
+
* A type that represents an array of JSON-compatible values.
|
|
43
|
+
* @param T - The type to be constrained
|
|
44
|
+
* @returns A constrained type that is compatible with JSON serialization.
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
export type JsonCompatibleArray<T> = Array<JsonCompatible<T>>;
|
|
32
48
|
/**
|
|
33
49
|
* Test if an `unknown` is a {@link JsonValue | JsonValue}.
|
|
34
50
|
* @param from - The `unknown` to be tested
|
|
@@ -40,8 +56,8 @@ export declare function isJsonPrimitive(from: unknown): from is JsonPrimitive;
|
|
|
40
56
|
/**
|
|
41
57
|
* Test if an `unknown` is potentially a {@link JsonObject | JsonObject}.
|
|
42
58
|
* @param from - The `unknown` to be tested.
|
|
43
|
-
* @returns `true` if the supplied parameter is a non-array, non-special object
|
|
44
|
-
* `false` otherwise.
|
|
59
|
+
* @returns `true` if the supplied parameter is a non-array, non-special object
|
|
60
|
+
* with no symbol keys, `false` otherwise.
|
|
45
61
|
* @public
|
|
46
62
|
*/
|
|
47
63
|
export declare function isJsonObject(from: unknown): from is JsonObject;
|
|
@@ -43,8 +43,8 @@ function isJsonPrimitive(from) {
|
|
|
43
43
|
/**
|
|
44
44
|
* Test if an `unknown` is potentially a {@link JsonObject | JsonObject}.
|
|
45
45
|
* @param from - The `unknown` to be tested.
|
|
46
|
-
* @returns `true` if the supplied parameter is a non-array, non-special object
|
|
47
|
-
* `false` otherwise.
|
|
46
|
+
* @returns `true` if the supplied parameter is a non-array, non-special object
|
|
47
|
+
* with no symbol keys, `false` otherwise.
|
|
48
48
|
* @public
|
|
49
49
|
*/
|
|
50
50
|
function isJsonObject(from) {
|
|
@@ -52,7 +52,11 @@ function isJsonObject(from) {
|
|
|
52
52
|
from !== null &&
|
|
53
53
|
!Array.isArray(from) &&
|
|
54
54
|
!(from instanceof RegExp) &&
|
|
55
|
-
!(from instanceof Date)
|
|
55
|
+
!(from instanceof Date) &&
|
|
56
|
+
!(from instanceof Map) &&
|
|
57
|
+
!(from instanceof Set) &&
|
|
58
|
+
!(from instanceof Error) &&
|
|
59
|
+
Object.getOwnPropertySymbols(from).length === 0);
|
|
56
60
|
}
|
|
57
61
|
/**
|
|
58
62
|
* Test if an `unknown` is potentially a {@link JsonArray | JsonArray}.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fgv/ts-json-base",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-27",
|
|
4
4
|
"description": "Typescript types and basic functions for working with json",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "dist/ts-json-base.d.ts",
|
|
@@ -18,33 +18,33 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/jest": "^29.5.14",
|
|
20
20
|
"@types/node": "^20.14.9",
|
|
21
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
22
|
-
"@typescript-eslint/parser": "^
|
|
23
|
-
"eslint": "^
|
|
21
|
+
"@typescript-eslint/eslint-plugin": "^8.42.0",
|
|
22
|
+
"@typescript-eslint/parser": "^8.42.0",
|
|
23
|
+
"eslint": "^9.35.0",
|
|
24
24
|
"eslint-plugin-import": "^2.32.0",
|
|
25
25
|
"eslint-plugin-node": "^11.1.0",
|
|
26
|
-
"eslint-plugin-promise": "^
|
|
26
|
+
"eslint-plugin-promise": "^7.2.1",
|
|
27
27
|
"jest": "^29.7.0",
|
|
28
28
|
"jest-extended": "^4.0.2",
|
|
29
|
-
"rimraf": "^
|
|
29
|
+
"rimraf": "^6.0.1",
|
|
30
30
|
"ts-jest": "^29.4.1",
|
|
31
31
|
"ts-node": "^10.9.2",
|
|
32
32
|
"typescript": "5.8.3",
|
|
33
|
-
"eslint-plugin-n": "^
|
|
34
|
-
"@rushstack/heft-node-rig": "2.9.
|
|
35
|
-
"@rushstack/heft": "0.74.
|
|
36
|
-
"@rushstack/heft-jest-plugin": "0.16.
|
|
33
|
+
"eslint-plugin-n": "^17.21.3",
|
|
34
|
+
"@rushstack/heft-node-rig": "2.9.5",
|
|
35
|
+
"@rushstack/heft": "0.74.4",
|
|
36
|
+
"@rushstack/heft-jest-plugin": "0.16.13",
|
|
37
37
|
"@types/heft-jest": "1.0.6",
|
|
38
38
|
"@microsoft/api-documenter": "^7.26.31",
|
|
39
39
|
"@rushstack/eslint-patch": "~1.12.0",
|
|
40
40
|
"@rushstack/eslint-config": "4.4.0",
|
|
41
41
|
"eslint-plugin-tsdoc": "~0.4.0",
|
|
42
|
-
"@fgv/ts-utils": "5.0.0-
|
|
43
|
-
"@fgv/ts-
|
|
44
|
-
"@fgv/ts-
|
|
42
|
+
"@fgv/ts-utils-jest": "5.0.0-27",
|
|
43
|
+
"@fgv/ts-extras": "5.0.0-27",
|
|
44
|
+
"@fgv/ts-utils": "5.0.0-27"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@fgv/ts-utils": "5.0.0-
|
|
47
|
+
"@fgv/ts-utils": "5.0.0-27"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "heft test --clean",
|