Home Reference Source Test

src/objects/toJSON.ts

/* eslint-disable unicorn/filename-case */
import { JSONObject, JSONValue } from '../types';
import { isRecord } from './isRecord';

/**
 * Serialises the object to a JSON representation.
 *
 * @param value
 */
export function toJSON(value: any): JSONValue | undefined { // eslint-disable-line @typescript-eslint/explicit-module-boundary-types
    switch (true) {
        case value && !! value.toJSON:
            return value.toJSON();
        case value && Array.isArray(value):
            return value.map(v => {
                return v === undefined
                    ? null
                    : toJSON(v) as JSONValue;
            });
        case value && value instanceof Map:
            return mapToJSON(value);
        case value && value instanceof Set:
            return toJSON(Array.from(value));
        case value && isRecord(value):
            return recordToJSON(value);
        case value && value instanceof Error:
            return errorToJSON(value);
        case isSerialisablePrimitive(value):
            return value;
        default:
            return JSON.stringify(value);
    }
}

function mapToJSON(map: Map<any, any>): JSONObject {
    const serialised = Array.from(map, ([key, value]) => [ toJSON(key), toJSON(value) ]);

    return Object.fromEntries(serialised);
}

function recordToJSON(value: Record<any, any>): JSONObject {
    const serialised = Object.entries(value)
        .map(([ k, v ]) => [ toJSON(k), toJSON(v) ]);

    return Object.fromEntries(serialised);
}

function errorToJSON(value: Error): JSONObject {
    return Object.getOwnPropertyNames(value)
        .reduce((serialised, key) => {
            serialised[key] = toJSON(value[key])
            return serialised;
        }, { }) as JSONObject;
}

function isSerialisableNumber(value: unknown): value is number {
    return typeof value === 'number'
        && ! Number.isNaN(value)
        && value !== Number.NEGATIVE_INFINITY
        && value !== Number.POSITIVE_INFINITY;
}

function isSerialisablePrimitive(value: unknown): value is string | boolean | number | null | undefined {
    if (['string', 'boolean'].includes(typeof value)) {
        return true;
    }

    if (value === null || value === undefined) {
        return true;
    }

    return isSerialisableNumber(value);
}