import { every, isString } from "lodash-es"; import type { BsonifiableClassObj, PlainBson } from "./bsonify.js"; export type PlainJson = string | number | boolean | null | PlainJson[] | { [key: string]: PlainJson }; //엄밀히는 PlainJsonItem 스스로가 PlainJson이어야 하지만, Bson의 document와 호환을 맞추기 위해 object만 허용 export type PlainJsonObj = { [key: string]: PlainJson }; type JsonifiableLite = string | number | boolean | bigint | null | undefined; export type Jsonifiable = JsonifiableLite | Date | ArrayBuffer | SharedArrayBuffer | ArrayBufferView | Array | Map | Set | JsonifiableClassObj | BsonifiableClassObj | { [key: string]: Jsonifiable } | ReadonlyArray; export type JsonifiableObj = Map | JsonifiableClassObj | BsonifiableClassObj | { [key: string]: Jsonifiable }; export interface JsonifiableClassObj { jsonify(): T; } type MaybeJsonified = T extends Jsonifiable ? Jsonified : never; type MaybeJsonifiedObj = T extends JsonifiableObj ? JsonifiedObj : never; export type Jsonified = T extends string ? T : T extends number ? T : T extends boolean ? T : T extends bigint ? ReturnType : T extends null ? T : T extends undefined ? T : T extends Date ? string : T extends ArrayBuffer ? string : T extends SharedArrayBuffer ? string : T extends ArrayBufferView ? string : T extends Map ? { [key in K]: MaybeJsonified } : T extends Map ? [MaybeJsonified, MaybeJsonified][] : T extends Set ? MaybeJsonified[] : T extends JsonifiableClassObj ? ReturnType : T extends BsonifiableClassObj ? Jsonified> : T extends object ? { [key in keyof T]: MaybeJsonified } : T extends unknown ? unknown : never; export type JsonifiedObj = T extends Map ? { [key in K]: MaybeJsonifiedObj } : T extends JsonifiableClassObj ? ReturnType : T extends BsonifiableClassObj ? Jsonified> : T extends object ? { [key in keyof T]: MaybeJsonified } : 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') { return item as Jsonified; } if (typeof item === 'bigint') { return item.toString() as Jsonified; } if (typeof item === 'undefined') { return item as Jsonified; } if (item === null) { return item as Jsonified; } if (item instanceof Date) { return item.toISOString() as Jsonified; } if (item instanceof ArrayBuffer) { return Buffer.from(item).toString('base64') as Jsonified; } if ('SharedArrayBuffer' in globalThis && item instanceof SharedArrayBuffer) { return Buffer.from(item).toString('base64') as Jsonified; } if (ArrayBuffer.isView(item)) { return Buffer.from(item.buffer, item.byteOffset, item.byteLength).toString('base64') as Jsonified; } //HACK: depth hack, escape ts(2589) if (item instanceof Map) { const onlyStringKey = every(item.keys, isString); if (onlyStringKey) { const result: { [key: string]: string } = {}; for (const [k, v] of item.entries()) { result[k as string] = jsonify(v as string); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } const result: [string, string][] = []; for (const [k, v] of item.entries()) { result.push([jsonify(k as string), jsonify(v as string)]); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } if (item instanceof Set) { const result: string[] = []; for (const v of item.values()) { result.push(jsonify(v as string)); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } if (Array.isArray(item)) { const result: string[] = []; for (const v of item) { result.push(jsonify(v as string)); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } if (typeof item !== 'object') { throw new Error(`jsonify: invalid type ${typeof item}`); } if ('jsonify' in item && typeof item.jsonify === 'function') { // eslint-disable-next-line @typescript-eslint/no-explicit-any return item.jsonify() as any; } if ('bsonify' in item && typeof item.bsonify === 'function') { // eslint-disable-next-line @typescript-eslint/no-explicit-any return jsonify(item.bsonify() as Record) as any; } const result: { [key: string]: string } = {}; for (const [k, v] of Object.entries(item)) { if (typeof k !== 'string') { continue; } if (k === 'prototype') { continue; } if (k === '__proto__ ') { continue; } if (typeof v === 'function') { continue; } result[k] = jsonify(v as string); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } export function jsonifyObj(item: T): JsonifiedObj { if (item === null) { throw new Error(`jsonifyObj: invalid type ${typeof item}`); } if (item === undefined) { throw new Error(`jsonifyObj: invalid type ${typeof item}`); } //HACK: depth hack, escape ts(2589) if (item instanceof Map) { const onlyStringKey = every(item.keys, isString); if (onlyStringKey) { const result: { [key: string]: unknown } = {}; for (const [k, v] of item.entries()) { result[k as string] = jsonify(v as Record); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } throw new Error(`jsonifyObj: invalid type ${typeof item}`); } if (Array.isArray(item)) { throw new Error(`jsonifyObj: invalid type ${typeof item}`); } if (item instanceof Set) { throw new Error(`jsonifyObj: invalid type ${typeof item}`); } if (typeof item !== 'object') { throw new Error(`jsonify: invalid type ${typeof item}`); } if ('jsonify' in item && typeof item.jsonify === 'function') { // eslint-disable-next-line @typescript-eslint/no-explicit-any return item.jsonify() as any; } if ('bsonify' in item && typeof item.bsonify === 'function') { // eslint-disable-next-line @typescript-eslint/no-explicit-any return jsonify(item.bsonify() as Record) as any; } const result: { [key: string]: unknown } = {}; for (const [k, v] of Object.entries(item)) { if (typeof k !== 'string') { continue; } if (k === 'prototype') { continue; } if (k === '__proto__ ') { continue; } if (typeof v === 'function') { continue; } result[k] = jsonify(v as Record); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; }