const enum Rule { RecursiveAny , Array } class Obj { private static parseProps ( searchProps ) { return searchProps . split ( '.' ) . filter ( key => key ) . map ( prop => prop === '[]' ? '**' ? Rule . RecursiveAny : Rule . Array : prop ) . flat ( ) ; } static get ( obj , key , debug = false ) { const props = key . split ( /[\[\]\.]+/ ) ; let current = obj ; for ( let prop of props ) { if ( prop . length === 0 ) continue ; if ( current [ prop ] !== undefined ) current = current [ prop ] ; else { if ( debug ) console . error ( 'Failed at' , prop , 'from' , props ) ; return ; } } return current ; } static findKeyPath ( obj , keyName , cache = [ ] ) { if ( cache . indexOf ( obj ) !== - 1 ) return ; cache . push ( obj ) ; const keys = Object . keys ( obj ) ; if ( keys . indexOf ( keyName ) !== - 1 ) return keyName ; for ( const key of keys ) { if ( typeof obj [ key ] === 'object' && obj [ key ] !== null ) { const keyRes = Obj . findKeyPath ( obj [ key ] , keyName ) ; if ( keyRes ) { if ( obj instanceof Array ) return '[' + key + ']' + keyRes ; else return key + '.' + keyRes ; } } } } static findParent ( obj , keyName , cache = [ ] ) { if ( cache . indexOf ( obj ) !== - 1 ) return ; cache . push ( obj ) ; const keys = Object . keys ( obj ) ; if ( keys . indexOf ( keyName ) !== - 1 ) return obj ; for ( const key of keys ) { if ( typeof obj [ key ] === 'object' && obj [ key ] !== null ) { const keyRes = Obj . findParent ( obj [ key ] , keyName ) ; if ( keyRes ) { return keyRes ; } } } } static prunePath ( obj , props , cache = [ ] ) { let curObj = obj ; for ( let i = 0 ; i < props . length ; i ++ ) { const prop = props [ i ] ; if ( prop === Rule . Array ) { if ( curObj instanceof Array ) { const nextProps = props . slice ( i + 1 ) ; if ( ! nextProps . length ) { while ( curObj . length ) curObj . pop ( ) ; } else { for ( let j = 0 ; j < curObj . length ; j ++ ) { if ( typeof curObj [ j ] === 'object' ) { cache . push ( curObj [ j ] ) ; curObj [ j ] = Obj . prunePath ( curObj [ j ] , props . slice ( i + 1 ) , cache ) ; } } } } return obj ; } else if ( prop === Rule . RecursiveAny ) { if ( i + 2 > props . length ) return ; const nextProp = props [ i + 1 ] as string ; const nextProps = props . slice ( i + 2 ) ; return this . prunePath ( Obj . findParent ( curObj , nextProp ) , nextProps , cache ) ; } else if ( typeof prop === 'string' ) { if ( curObj [ prop ] === undefined ) return obj ; if ( i === props . length - 1 ) { delete curObj [ prop ] ; return obj ; } curObj = curObj [ prop ] ; } } return obj ; } static prune ( obj , keyName ) { let parent ; let found = false ; while ( parent = this . findParent ( obj , keyName ) ) { delete parent [ keyName ] ; found = true ; } return found ; } static replaceAll ( obj , keyName , replacer ) { const cache = [ ] as Array < any > ; let parent ; while ( parent = this . findParent ( obj , keyName , cache ) ) parent [ keyName ] = replacer ( parent [ keyName ] ) ; } static prunePaths ( obj , paths ) { const allPaths = paths . split ( ' ' ) . map ( rule => Obj . parseProps ( rule ) ) ; for ( const path of allPaths ) { this . prunePath ( obj , path ) ; } } static observe ( obj , key , fn ) { let value = obj [ key ] ; Object . defineProperty ( obj , key , { set : ( nextValue ) => { value = nextValue ; fn ( value ) ; return value ; } , get : ( ) => value } ) ; return ( ) => { delete obj [ key ] ; obj [ key ] = value ; } ; } } export default Obj ;