import { every, isString } from "lodash-es"; type JsonifiableLite = string | number | boolean | bigint | null | undefined | Date | ArrayBuffer | SharedArrayBuffer | ArrayBufferView | JsonifiableLite[] | { [key: string]: JsonifiableLite }; export type Jsonifiable = JsonifiableLite | Jsonifiable[] | Map | Set | { jsonify(): Jsonifiable } | { [key: string]: Jsonifiable }; export type Jsonified = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends bigint ? string : T extends null ? null : T extends undefined ? undefined : T extends Date ? string : T extends ArrayBuffer ? string : T extends SharedArrayBuffer ? string : T extends ArrayBufferView ? string : T extends (infer K extends Jsonifiable)[] ? Jsonified[] : T extends Map ? ( K extends string ? { [key in string]: Jsonified } : [Jsonified, Jsonified][]) : T extends Set ? Jsonified[] : T extends object ? ( T extends { jsonify(): infer V extends Jsonifiable } ? Jsonified : //HACK: jsonify()는 Jsonified여야 함 {[key in keyof T]: Jsonified}): T extends unknown ? unknown : never; /** * Convert any object to JSON-safe object */ export function jsonify(item: T): Jsonified { if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') { // eslint-disable-next-line @typescript-eslint/no-explicit-any return item as any; } if (typeof item === 'bigint') { // eslint-disable-next-line @typescript-eslint/no-explicit-any return item.toString() as any; } if (item === undefined || item === null) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return item as any; } if (item instanceof Date) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return item.toISOString() as any; } if (item instanceof ArrayBuffer || item instanceof SharedArrayBuffer) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return Buffer.from(item).toString('base64') as any; } if (ArrayBuffer.isView(item)) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return Buffer.from(item.buffer, item.byteOffset, item.byteLength).toString('base64') as any; } if (Array.isArray(item)) { //HACK: depth hack const result: Jsonified[] = []; for (const v of item) { result.push(jsonify(v as string) as Jsonified); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } if (item instanceof Map) { //HACK: depth hack const onlyStringKey = every(item.keys, isString); if (onlyStringKey) { const result: { [key: string]: Jsonified } = {}; for (const [k, v] of item.entries()) { result[k as string] = jsonify(v as string) as Jsonified; } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } const result: [Jsonified, Jsonified][] = []; for (const [k, v] of item.entries()) { result.push([jsonify(k as string), jsonify(v as string)] as [Jsonified, Jsonified]); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } if (item instanceof Set) { const result: Jsonified[] = []; for (const v of item.values()) { result.push(jsonify(v as string) as Jsonified); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } if (typeof item === 'object') { if (typeof (item as { jsonify(): unknown }).jsonify === 'function') { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (item as { jsonify(): unknown }).jsonify() as any; } const result: { [key: string]: Jsonified } = {}; for (const [k, v] of Object.entries(item)) { if (typeof k !== 'string') { continue; } if (typeof v === 'function') { continue; } result[k] = jsonify(v as string) as Jsonified; } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } throw new Error(`jsonify: invalid type ${typeof item}`); }