Check if its of type ‘object’
Arrays and objects are both of type “Object”, so if the inObject is neither an array nor object, we exit because we only deep copy objects and arrays.
1 2 3 4 5 6 7 8 9 |
const myDeepCopy = inObject => { console.log('-- myDeepCopy --', inObject) if (typeof inObject !== "object" || inObject === null) { return inObject; } ... ... } |
Initialize to [] or {} depending on whether its an array or object
Initialization Phase. If the incoming object is an array, we initialize to an empty array [].
If its an object, we initialize our source to empty literal object {}
1 2 3 4 5 6 7 8 9 |
let outObject; if (Array.isArray(inObject)) { console.log('incoming object is an array!') outObject = [] } else { console.log('incoming object is an object!') outObject = {}; } |
iterate over the index/key
Now that we have a correctly initialized empty data structure, we go on to doing the copying.
In JS, using for (key in obj) works for both object/arrays.
If its an array, each key is the element index
If its an object, then its key/value as is
1 2 3 4 5 6 7 8 9 |
for (key in inObject) { console.log('key', key) console.log('value', inObject[key]) let value = inObject[key] // typically, we'd do this outObject[key] = value } |
Hence, say we create an array pass it in. Since all elements in the array are primitives, we do normal copying via assignment operator (=)
-- myDeepCopy -- [ 'a', 'b', 'c', 'd', 'e' ]
incoming object is an array!
key 0
value a
key 1
value b
key 2
value c
key 3
value d
key 4
value e
outObject [ 'a', 'b', 'c', 'd', 'e' ]
But! there’s a problem
If one of the values is an object, we’d be doing a shallow copy because the assignment operator (=) simply points the array[i] or obj[i] reference to the object.
1 |
outObject[key] = value // re-pointing our reference |
This means we’re simply just pointing our outObject[key] to the same object value (inObject[key]) is pointing to. Hence, we dont’ have our own copy.
Let’s look at a test case here:
1 2 3 4 5 6 7 8 9 10 11 |
let obj = { name: 'ricky', age: 40, luckyNumbers: [6, 8] } let destObj = myDeepCopy(obj) obj.luckyNumbers[0] = 8 // change first element to 8 console.log('src object', obj) console.log('destination obj', destObj) |
In our test, we have our src obj change its array luckyNumbers. However, we see the same change in our destination object. This proves that the two arrays’ luckyNumbers reference are pointing to the same array. This is an example of shallow copy.
-- myDeepCopy -- { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }
incoming object is an object!
key name
value ricky
key age
value 40
key luckyNumbers
value [ 6, 8 ]
outObject { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }
src object { name: 'ricky', age: 40, luckyNumbers: [ 8, 8 ] }
destination obj { name: 'ricky', age: 40, luckyNumbers: [ 8, 8 ] }
In order to make our own copy, we use recursion and let our ‘Initialization Phase’
create the deep initial object first, then copy over the primitives one by one.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const myDeepCopy = inObject => { if (typeof inObject !== "object" || inObject === null) { return inObject; } let outObject; if (Array.isArray(inObject)) { outObject = [] } else { outObject = {}; } if (typeof value === 'object' && value !== null) { outObject[key] = myDeepCopy(value) // use recursion to do deep copy if its an objs/arrays } else { outObject[key]= value // copy over primitives } ... ... |
where recursion executes myDeepCopy with array luckyNumbers [6, 8].
Then in that myDeepCopy, we allocate our own array to use during the Initialization Phase. We don’t want to share with others
1 2 3 4 5 |
if (Array.isArray(inObject)) { outObject = [] } else { outObject = {}; } |
Now when you run your test code, you’ll see that changes do the source won’t affect the destination:
-- myDeepCopy -- { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }
incoming object is an object!
key name
value ricky
key age
value 40
key luckyNumbers
value [ 6, 8 ]
-- myDeepCopy -- [ 6, 8 ]
incoming object is an array!
key 0
value 6
key 1
value 8
outObject [ 6, 8 ]
outObject { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }
src object { name: 'ricky', age: 40, luckyNumbers: [ 8, 8 ] }
destination obj { name: 'ricky', age: 40, luckyNumbers: [ 6, 8 ] }
Full Source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
// given an array or object const myDeepCopy = inObject => { console.log('-- myDeepCopy --', inObject) // array and objects are both of type "Object" // so if the inObject is neither an array nor object, // we exit because we only deep copy objects and arrays if (typeof inObject !== "object" || inObject === null) { return inObject; } // Initialization Phase. If incoming object is an array, we initialize to empty array [] // if its an object, we initialize our source to empty literal object {} let outObject; if (Array.isArray(inObject)) { console.log('incoming object is an array!') outObject = [] } else { console.log('incoming object is an object!') outObject = {}; } // if its an array, each key is the element index // if its an object, then its key/value as is for (key in inObject) { console.log('key', key) console.log('value', inObject[key]) let value = inObject[key] // typically, we'd do this //outObject[key] = value // But! there's a problem // if value is an object, we're doing a shallow copy here. Which means we're simply just pointing our // outObject[key] to the same object inObject[key] is pointing to. Hence, we don't have our own copy. // in order to make our own copy, we use recursion and let our 'Initialization Phase' // create the deep initial object first // check if value is object, is referencing valid data if (typeof value === 'object' && value !== null) { outObject[key] = myDeepCopy(value) // if objects, we must deep copy and allocate our own objs } else { outObject[key]= value // primitves just copy like so } } console.log('outObject', outObject) return outObject } let obj = { name: 'ricky', age: 40, luckyNumbers: [6, 8] } let destObj = myDeepCopy(obj) obj.luckyNumbers[0] = 8 console.log('src object', obj) console.log('destination obj', destObj) |