UNPKG

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