forked from sindresorhus/type-fest
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jsonify.d.ts
75 lines (59 loc) · 2.78 KB
/
jsonify.d.ts
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
import {JsonPrimitive, JsonValue} from './basic';
// Note: The return value has to be `any` and not `unknown` so it can match `void`.
type NotJsonable = ((...args: any[]) => any) | undefined;
/**
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 be satisfy `JsonValue`.
@example
```
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 transforms Date into value assignable to JsonValue
```
Non-JSON values such as `Date` implement `.toJSON()`, so they can be transformed to a value assignable to `JsonValue`:
@example
```
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
*/
type Jsonify<T> =
// Check if there are any non-JSONable types represented in the union.
// Note: The use of tuples in this first condition side-steps distributive conditional types
// (see https://github.com/microsoft/TypeScript/issues/29368#issuecomment-453529532)
[Extract<T, NotJsonable>] extends [never]
? T extends JsonPrimitive
? T // Primitive is acceptable
: T extends Array<infer U>
? Array<Jsonify<U>> // It's an array: recursive call for its children
: T extends object
? T extends {toJSON(): infer J}
? (() => J) extends (() => JsonValue) // Is J assignable to JsonValue?
? J // Then T is Jsonable and its Jsonable value is J
: never // Not Jsonable because its toJSON() method does not return JsonValue
: {[P in keyof T]: Jsonify<Required<T>[P]>} // It's an object: recursive call for its children
: never // Otherwise any other non-object is removed
: never; // Otherwise non-JSONable type union was found not empty