import { IObj , IResult } from '../interfaces' ; import type { IStringifier , IParser } from './jsonCache.types' ; import { parseKey , encodeKey , splitKey } from '../utils/key' ; import { getTypeOf , getTypedVal , getValueOf , isSkippedType } from '../utils/type' ; const getDefaultResult = ( ) : IResult => ( { data : { } , typeInfo : { } , arrayInfo : { } , } ) ; internal export interface IFlattener { flatten ( obj ) : IResult ; unflatten ( result ) : IObj ; } internal export class Flattener implements IFlattener { constructor ( private stringifier = { } , private parser = { } ) { } param obj public flatten ( obj ) { return this . traverse ( obj , '' , getDefaultResult ( ) ) ; } param flattened public unflatten ( flattened ) { const typedData = this . mergeTypes ( flattened ) ; let result ; Object . entries ( typedData ) . some ( ( [ key , val ] ) => { if ( ! key ) { if ( Object . keys ( typedData ) . length <= 1 ) { result = val ; return true ; } else { return false ; } } const splittedKeys = splitKey ( key ) ; if ( ! result ) { result = typeof parseKey ( splittedKeys [ 0 ] ) === 'number' ? [ ] : { } ; } this . scaffoldStructure ( result , splittedKeys , val ) ; return false ; } ) ; return result ; } /***********************************
     * PRIVATE METHODS - Flatten helpers
     **********************************/ private traverse ( target , basePath , result ) { if ( ! ( target instanceof Object ) ) return result ; if ( Array . isArray ( target ) ) { result . arrayInfo [ basePath ] = true ; } const entries = Object . entries ( target ) ; if ( entries . length > 0 ) { entries . forEach ( ( [ key , val ] ) => { const encodedKey = encodeKey ( key ) ; const path = appendPath ( basePath , encodedKey ) ; if ( val instanceof Object ) { this . traverse ( val , path , result ) ; } else { this . assignResult ( result , path , val ) ; } } ) ; } else { this . assignResult ( result , basePath , target ) ; } return result ; } private assignResult ( result , path , val ) { if ( ! isSkippedType ( val ) ) { result . data [ path ] = getValueOf ( val , this . stringifier ) ; result . typeInfo [ path ] = getTypeOf ( val ) ; } } /*************************************
     * PRIVATE METHODS - Unflatten helpers
     *************************************/ private mergeTypes ( result ) { const { data , typeInfo } = result ; return Object . entries ( data ) . reduce ( ( merged , [ path , val ] ) => { merged [ path ] = getTypedVal ( typeInfo [ path ] , val , this . parser ) ; return merged ; } , { } ) ; } private scaffoldStructure ( tree , splittedKeys , val ) { for ( let i = 0 , len = splittedKeys . length ; i < len ; i ++ ) { const k1 = parseKey ( splittedKeys [ i ] ) ; const k2 = parseKey ( splittedKeys [ i + 1 ] ) ; if ( typeof k2 === 'undefined' ) { tree [ k1 ] = val ; } else { const isObj = typeof tree [ k1 ] === 'object' ; if ( ! isObj ) tree [ k1 ] = typeof k2 === 'number' ? [ ] : { } ; tree = tree [ k1 ] ; } } } } function appendPath ( basePath , key ) { return basePath ? `${ basePath }.${ key }` : key ; }