import { getChangeValue , isChange } from '../-private/change' ; import normalizeObject from './normalize-object' ; import { isArrayObject , objectToArray , arrayToObject } from './array-object' ; interface Options { safeGet : any ; safeSet : any ; propertyIsUnsafe ? : any ; getKeys ? : ( record : Record < string , unknown > ) => string [ ] ; } function isNonNullObject ( value ) { return ! ! value && typeof value === 'object' ; } function isSpecial ( value ) { let stringValue = Object . prototype . toString . call ( value ) ; return stringValue === '[object RegExp]' || stringValue === '[object Date]' ; } function isMergeableObject ( value ) { return isNonNullObject ( value ) && ! isSpecial ( value ) ; } function getEnumerableOwnPropertySymbols ( target ) { return Object . getOwnPropertySymbols ? Object . getOwnPropertySymbols ( target ) . filter ( symbol => { return target . propertyIsEnumerable ( symbol ) ; } ) : [ ] ; } function getKeys ( target ) { return Object . keys ( target ) . concat ( getEnumerableOwnPropertySymbols ( target ) ) ; } function propertyIsOnObject ( object , property ) { try { return property in object ; } catch ( _ ) { return false ; } } export function propertyIsUnsafe ( target , key ) { return ( propertyIsOnObject ( target , key ) && ! ( Object . hasOwnProperty . call ( target , key ) && Object . propertyIsEnumerable . call ( target , key ) ) ) ; } method function buildPathToValue ( source , options , kv , possibleKeys ) { Object . keys ( source ) . forEach ( ( key ) : void => { let possible = source [ key ] ; if ( possible && isChange ( possible ) ) { kv [ [ ... possibleKeys , key ] . join ( '.' ) ] = getChangeValue ( possible ) ; return ; } if ( possible && typeof possible === 'object' ) { buildPathToValue ( possible , options , kv , [ ... possibleKeys , key ] ) ; } } ) ; return kv ; } method function mergeTargetAndSource ( target , source , options ) { options . getKeys ( source ) . forEach ( key => { if ( options . propertyIsUnsafe ( target , key ) ) { if ( options . safeSet ) { const kv = buildPathToValue ( source , options , { } , [ ] ) ; if ( Object . keys ( kv ) . length > 0 ) { for ( key in kv ) { const val = kv [ key ] ; options . safeSet ( target , key , val ) ; } } } return ; } if ( propertyIsOnObject ( target , key ) && isMergeableObject ( source [ key ] ) && ! isChange ( source [ key ] ) ) { options . safeSet ( target , key , mergeDeep ( options . safeGet ( target , key ) , options . safeGet ( source , key ) , options ) ) ; } else { let next = source [ key ] ; if ( next && isChange ( next ) ) { return options . safeSet ( target , key , getChangeValue ( next ) ) ; } return options . safeSet ( target , key , normalizeObject ( next ) ) ; } } ) ; return target ; } method export default function mergeDeep ( target , source , options = { safeGet : undefined , safeSet : undefined , propertyIsUnsafe : undefined , getKeys : undefined } ) { options . getKeys = options . getKeys || getKeys ; options . propertyIsUnsafe = options . propertyIsUnsafe || propertyIsUnsafe ; options . safeGet = options . safeGet || function ( obj , key ) : Record < string , any > { return obj [ key ] ; } ; options . safeSet = options . safeSet || function ( obj , key , value ) : any { return ( obj [ key ] = value ) ; } ; let sourceIsArray = Array . isArray ( source ) ; let targetIsArray = Array . isArray ( target ) ; let sourceAndTargetTypesMatch = sourceIsArray === targetIsArray ; if ( ! sourceAndTargetTypesMatch ) { let sourceIsArrayLike = isArrayObject ( source ) ; if ( targetIsArray && sourceIsArrayLike ) { return objectToArray ( mergeTargetAndSource ( arrayToObject ( target ) , source , options as Required < Options > ) ) ; } return source ; } else if ( sourceIsArray ) { return source ; } else if ( target === null || target === undefined ) { /**
         * If the target was set to null or undefined, we always want to return the source.
         * There is nothing to merge.
         *
         * Without this explicit check, typeof null === typeof {any object-like thing}
         * which means that mergeTargetAndSource will be called, and you can't merge with null
         */ return source ; } else { return mergeTargetAndSource ( target , source , options as Required < Options > ) ; } }