-
-
Notifications
You must be signed in to change notification settings - Fork 679
Expand file tree
/
Copy pathjsonify.d.ts
More file actions
128 lines (105 loc) · 4.4 KB
/
jsonify.d.ts
File metadata and controls
128 lines (105 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import type {JsonPrimitive, JsonValue} from './json-value.d.ts';
import type {EmptyObject} from './empty-object.d.ts';
import type {UndefinedToOptional} from './internal/index.d.ts';
import type {IsAny} from './is-any.d.ts';
import type {IsNever} from './is-never.d.ts';
import type {IsUnknown} from './is-unknown.d.ts';
import type {NegativeInfinity, PositiveInfinity} from './numeric.d.ts';
import type {TypedArray} from './typed-array.d.ts';
import type {UnknownArray} from './unknown-array.d.ts';
// Note: The return value has to be `any` and not `unknown` so it can match `void`.
type NotJsonable = ((...arguments_: any[]) => any) | undefined | symbol;
type NeverToNull<T> = IsNever<T> extends true ? null : T;
type UndefinedToNull<T> = T extends undefined ? null : T;
// Handles tuples and arrays
type JsonifyList<T extends UnknownArray> = T extends readonly []
? []
: T extends readonly [infer F, ...infer R]
? [F, ...R] extends T // With TS 5.8.3, if `string[] & ['foo']`, `R` is `unknown[]` here, making the inferred types not equal to the original one
? [NeverToNull<Jsonify<F>>, ...JsonifyList<R>]
: [NeverToNull<Jsonify<F>>]
: IsUnknown<T[number]> extends true
? JsonValue[]
: Array<T[number] extends NotJsonable ? null : Jsonify<UndefinedToNull<T[number]>>>;
type FilterJsonableKeys<T extends object> = {
[Key in keyof T]: T[Key] extends NotJsonable ? never : Key;
}[keyof T];
/**
JSON serialize objects (not including arrays) and classes.
*/
type JsonifyObject<T extends object> = {
[Key in keyof Pick<T, FilterJsonableKeys<T>>]: Jsonify<T[Key]>;
};
/**
Transform a type to one that is assignable to the `JsonValue` type.
This includes:
1. Transforming JSON `interface` to a `type` that is assignable to `JsonValue`.
2. Transforming non-JSON value that is *jsonable* to a type that is assignable to `JsonValue`, where *jsonable* means the non-JSON value implements the `.toJSON()` method that returns a value that is assignable to `JsonValue`.
@remarks
An interface cannot be structurally compared to `JsonValue` because an interface can be re-opened to add properties that may not satisfy `JsonValue`.
@example
```
import type {Jsonify, JsonValue} from 'type-fest';
interface Geometry {
type: 'Point' | 'Polygon';
coordinates: [number, number];
}
const point: Geometry = {
type: 'Point',
coordinates: [1, 1]
};
const problemFn = (data: JsonValue) => {
// Does something with data
};
problemFn(point); // Error: type Geometry is not assignable to parameter of type JsonValue because it is an interface
const fixedFn = <T>(data: Jsonify<T>) => {
// Does something with data
};
fixedFn(point); // Good: point is assignable. Jsonify<T> transforms Geometry into value assignable to JsonValue
fixedFn(new Date()); // Error: As expected, Date is not assignable. Jsonify<T> cannot transform Date into a value assignable to JsonValue
```
Non-JSON values such as `Date` implement `.toJSON()`, so they can be transformed to a value assignable to `JsonValue`:
@example
```
import type {Jsonify} from 'type-fest';
const time = {
timeValue: new Date()
};
// `Jsonify<typeof time>` is equivalent to `{timeValue: string}`
const timeJson = JSON.parse(JSON.stringify(time)) as Jsonify<typeof time>;
```
@link https://github.com/Microsoft/TypeScript/issues/1897#issuecomment-710744173
@category JSON
*/
export type Jsonify<T> = IsAny<T> extends true
? any
: T extends PositiveInfinity | NegativeInfinity
? null
: T extends JsonPrimitive
? T
: // Any object with toJSON is special case
T extends {toJSON(): infer J}
? (() => J) extends () => JsonValue // Is J assignable to JsonValue?
? J // Then T is Jsonable and its Jsonable value is J
: Jsonify<J> // Maybe if we look a level deeper we'll find a JsonValue
: // Instanced primitives are objects
T extends Number
? number
: T extends String
? string
: T extends Boolean
? boolean
: T extends Map<any, any> | Set<any>
? EmptyObject
: T extends TypedArray
? Record<string, number>
: T extends NotJsonable
? never // Non-JSONable type union was found not empty
: T extends UnknownArray
? JsonifyList<T>
: T extends object
? JsonifyObject<UndefinedToOptional<T>> // JsonifyObject recursive call for its children
: IsUnknown<T> extends true
? JsonValue
: never; // Otherwise any other non-object is removed
export {};