UNPKG

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