stack

Home > @infiniteobjects/type-utils > Jsonify

Jsonify type

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.

Signature:

export type Jsonify<T> = [
    Extract<T, NotJsonable>
] extends [never] ? T extends JsonPrimitive ? T : T extends Array<infer U> ? Array<Jsonify<U>> : T extends object ? T extends {
    toJSON(): infer J;
} ? (() => J) extends () => JsonValue ? J : never : {
    [P in keyof T]: Jsonify<T[P]>;
} : never : never;

References: JsonPrimitive, Jsonify, 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. Non-JSON values such as Date implement .toJSON(), so they can be transformed to a value assignable to JsonValue:

Example 1

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

Example 2

const time = {
    timeValue: new Date()
};

// `Jsonify<typeof time>` is equivalent to `{timeValue: string}`
const timeJson = JSON.parse(JSON.stringify(time)) as Jsonify<typeof time>;