import Change , { getChangeValue , isChange } from '../-private/change' ; import isObject from './is-object' ; import { isArrayObject } from './array-object' ; interface Options { safeSet ? : any ; safeGet ? : any ; } function split ( path ) { const keys = path . split ( '.' ) ; return keys ; } function findSiblings ( target , keys ) { const [ leafKey ] = keys . slice ( - 1 ) ; const remaining = Object . keys ( target ) . filter ( k => k !== leafKey ) . reduce ( ( acc , key ) => { acc [ key ] = target [ key ] ; return acc ; } , Object . create ( null ) ) ; return { ... remaining } ; } function isValidKey ( key ) { return key !== '__proto__' && key !== 'constructor' && key !== 'prototype' ; } method export default function setDeep ( target , path , value , options = { safeSet : undefined , safeGet : undefined } ) { const keys = split ( path ) . filter ( isValidKey ) ; let orig = target ; options . safeSet = options . safeSet || function ( obj , key , value ) : any { return ( obj [ key ] = value ) ; } ; options . safeGet = options . safeGet || function ( obj , key ) : any { return obj ? obj [ key ] : obj ; } ; if ( keys . length === 1 ) { options . safeSet ( target , path , value ) ; return target ; } for ( let i = 0 ; i < keys . length ; i ++ ) { let prop = keys [ i ] ; if ( Array . isArray ( target ) && parseInt ( prop , 10 ) < 0 ) { throw new Error ( 'Negative indices are not allowed as arrays do not serialize values at negative indices' ) ; } const isObj = isObject ( options . safeGet ( target , prop ) ) ; const isArray = Array . isArray ( options . safeGet ( target , prop ) ) ; const isComplex = isObj || isArray ; if ( ! isComplex ) { options . safeSet ( target , prop , { } ) ; } else if ( isComplex && isChange ( options . safeGet ( target , prop ) ) ) { let changeValue = getChangeValue ( options . safeGet ( target , prop ) ) ; if ( isObject ( changeValue ) ) { const siblings = findSiblings ( changeValue , keys ) ; const resolvedValue = isChange ( value ) ? getChangeValue ( value ) : value ; const isArrayLike = Array . isArray ( target ) || isArrayObject ( target ) ; const nestedKeys = isArrayLike ? keys . slice ( i + 1 , keys . length ) . join ( '.' ) : keys . slice ( 1 , keys . length ) . join ( '.' ) ; let newValue ; if ( isArrayLike && undefined === resolvedValue ) { newValue = resolvedValue ; } else if ( i === keys . length - 1 ) { newValue = resolvedValue ; } else { newValue = setDeep ( siblings , nestedKeys , resolvedValue , options ) ; } options . safeSet ( target , prop , new Change ( newValue ) ) ; break ; } else { options . safeSet ( target , prop , { } ) ; } } if ( i === keys . length - 1 ) { options . safeSet ( target , prop , value ) ; break ; } target = options . safeGet ( target , prop ) ; } return orig ; }