feat(smartjson): Implement stableOneWayStringify: deterministic, cycle-safe JSON for hashing/comparisons; update docs and tests
This commit is contained in:
		@@ -3,6 +3,6 @@
 | 
			
		||||
 */
 | 
			
		||||
export const commitinfo = {
 | 
			
		||||
  name: '@push.rocks/smartjson',
 | 
			
		||||
  version: '5.1.0',
 | 
			
		||||
  version: '5.2.0',
 | 
			
		||||
  description: 'A library for handling typed JSON data, providing functionalities for parsing, stringifying, and working with JSON objects, including support for encoding and decoding buffers.'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										63
									
								
								ts/index.ts
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								ts/index.ts
									
									
									
									
									
								
							@@ -26,6 +26,69 @@ export const stringifyJsonL = (items: any[]): string => {
 | 
			
		||||
  return items.map((item) => stringify(item)).join('\n');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * stableOneWayStringify
 | 
			
		||||
 * - Produces a stable, deterministic JSON string
 | 
			
		||||
 * - Handles circular references without throwing (replaces cycles)
 | 
			
		||||
 * - Safe for hashing/comparisons ("one-way"; not intended for round-trips)
 | 
			
		||||
 */
 | 
			
		||||
export const stableOneWayStringify = (
 | 
			
		||||
  objArg: any,
 | 
			
		||||
  simpleOrderArray?: string[],
 | 
			
		||||
  optionsArg: plugins.IStableJsonTypes['Options'] = {}
 | 
			
		||||
): string => {
 | 
			
		||||
  // Prepare object without throwing on circular references, and encode buffers
 | 
			
		||||
  const visited = new WeakSet<object>();
 | 
			
		||||
  const sanitize = (val: any): any => {
 | 
			
		||||
    // primitives
 | 
			
		||||
    if (val === null || typeof val !== 'object') {
 | 
			
		||||
      return val;
 | 
			
		||||
    }
 | 
			
		||||
    // Encode buffers/typed arrays via existing replacer
 | 
			
		||||
    const replaced = (bufferhandling.replacer as any)('', val);
 | 
			
		||||
    if (replaced && replaced.type === 'EncodedBuffer' && typeof replaced.data === 'string') {
 | 
			
		||||
      return replaced;
 | 
			
		||||
    }
 | 
			
		||||
    // Handle circular references
 | 
			
		||||
    if (visited.has(val)) {
 | 
			
		||||
      return '__cycle__';
 | 
			
		||||
    }
 | 
			
		||||
    visited.add(val);
 | 
			
		||||
    // Arrays
 | 
			
		||||
    if (Array.isArray(val)) {
 | 
			
		||||
      return val.map((item) => sanitize(item));
 | 
			
		||||
    }
 | 
			
		||||
    // Plain objects and class instances: copy enumerable own props
 | 
			
		||||
    const out: Record<string, any> = {};
 | 
			
		||||
    for (const key of Object.keys(val)) {
 | 
			
		||||
      try {
 | 
			
		||||
        out[key] = sanitize((val as any)[key]);
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        // In case of getters throwing or non-serializable, mark
 | 
			
		||||
        out[key] = '__unserializable__';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const obj = sanitize(objArg);
 | 
			
		||||
  const options: plugins.IStableJsonTypes['Options'] = {
 | 
			
		||||
    ...optionsArg,
 | 
			
		||||
    cycles: true,
 | 
			
		||||
  };
 | 
			
		||||
  if (simpleOrderArray && !options.cmp) {
 | 
			
		||||
    const order = new Map<string, number>();
 | 
			
		||||
    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;
 | 
			
		||||
      return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  return plugins.stableJson(obj, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param objArg
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user