import * as plugins from './smartjson.plugins.js'; import * as bufferhandling from './bufferhandling.js'; interface JsonObject { [key: string]: any; } /** * allows you to parse a json */ export const parse = bufferhandling.parse; export const parseJsonL = (jsonlData: string): JsonObject[] => { const lines = jsonlData.split('\n'); const parsedData: JsonObject[] = lines.reduce((acc, line) => { const trimmed = line.trim(); if (trimmed.length > 0) { acc.push(parse(trimmed)); } return acc; }, [] as JsonObject[]); return parsedData; } export const stringifyJsonL = (items: any[]): string => { return items.map((item) => stringify(item)).join('\n'); } /** * * @param objArg * @param optionsArg */ export const stringify = ( objArg: any, simpleOrderArray?: string[], optionsArg: plugins.IStableJsonTypes['Options'] = {} ): string => { const bufferedJson = bufferhandling.stringify(objArg); objArg = JSON.parse(bufferedJson); // derive a simple comparator from simpleOrderArray if provided and no custom cmp supplied let options = { ...optionsArg }; if (simpleOrderArray && !options.cmp) { const order = new Map(); simpleOrderArray.forEach((key, idx) => order.set(key, idx)); options.cmp = (a, b) => { const aIdx = order.has(a.key) ? (order.get(a.key) as number) : Number.POSITIVE_INFINITY; const bIdx = order.has(b.key) ? (order.get(b.key) as number) : Number.POSITIVE_INFINITY; if (aIdx !== bIdx) return aIdx - bIdx; // fallback to lexicographic order for stable behavior return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; }; } let returnJson = plugins.stableJson(objArg, options); return returnJson; }; export const stringifyPretty = (objectArg: any) => { const stringified = stringify(objectArg); const object = JSON.parse(stringified); return JSON.stringify(object, null, 2); } export const stringifyBase64 = (...args: Parameters): string => { const stringifiedResult = stringify(...args); return plugins.smartstring.base64.encodeUri(stringifiedResult); }; export const parseBase64 = (base64JsonStringArg: string) => { const base64 = plugins.smartstring.base64 as any; const decodeFn: (input: string) => string = base64.decodeUri || base64.decode; const simpleStringified = decodeFn(base64JsonStringArg); return parse(simpleStringified); }; export class Smartjson { /** * enfolds data from an object */ public static enfoldFromObject(this: T, objectArg: any): InstanceType { const newInstance = new this() as InstanceType; const saveables: string[] = (newInstance as any).saveableProperties || []; for (const keyName in objectArg) { if (saveables.indexOf(keyName) !== -1) { (newInstance as any)[keyName] = objectArg[keyName]; } } return newInstance; } /** * enfold from json */ public static enfoldFromJson(this: T, jsonArg: string): InstanceType { const objectFromJson = parse(jsonArg); return this.enfoldFromObject(objectFromJson); } // ======== // INSTANCE // ======== public saveableProperties: string[]; /** * folds a class into an object */ public foldToObject() { const trackSet = new Set(); trackSet.add(this); return this.foldToObjectInternal(trackSet); } private foldToObjectInternal(trackSet: Set) { const result: { [key: string]: any } = {}; const foldValue = (val: any): any => { if (val instanceof Smartjson) { if (trackSet.has(val)) { throw new Error('cycle detected'); } trackSet.add(val); return val.foldToObjectInternal(trackSet); } if (Array.isArray(val)) { return val.map((item) => foldValue(item)); } return plugins.lodashCloneDeep(val); }; const props: string[] = (this as any).saveableProperties || []; for (const keyName of props) { const value = this[keyName]; result[keyName] = foldValue(value); } return result; } /** * folds a class into an object */ public foldToJson() { const foldedObject = this.foldToObject(); return stringify(foldedObject); } } /** * Decorator that marks a property as foldable */ export const foldDec = () => { return (target: any, key: string) => { if (!target.saveableProperties) { target.saveableProperties = []; } target.saveableProperties.push(key); }; }; export const deepEqualObjects = (object1: any, object2: any): boolean => { const object1String = stringify(object1); const object2String = stringify(object2); return object1String === object2String; }; export const deepEqualJsonLStrings = (jsonLString1: string, jsonLString2: string): boolean => { const firstArray = parseJsonL(jsonLString1); const secondArray = parseJsonL(jsonLString2); return deepEqualObjects(firstArray, secondArray); }