Reputation: 153
This is the console output of two different "arrays" made up of the same strings. I do not know how they originated (there is lots of code that I did not write) but I believe only Object2 is a true Array as it has a length function. Calling Object1.length gives 0.
Even though Object1 is called an Array in the console, it appears to actually be an Object with numerical attributes. How do I ensure the Object1 type of array is never used and all arrays look like Object2?
Object1
Array {0: 'ABC', 1: 'IJK', 2: 'XYZ'}
0: "ABC"
1: "IJK"
2: "XYZ"
[[Prototype]]: Array(0)
Object2
(3) ['ABC', 'IJK', 'XYZ']
0: "ABC"
1: "IJK"
2: "XYZ"
length: 3
Edit 1: Object1 and Object2 are just variable names I used for this example.
Calling Array.isArray(Object1) = false while Array.isArray(Object2) = True. How would one even make an Object called Array? [This was answered by CRice in the comments. See Edit 3]
Edit 2: The code is in an AngularJS monstrosity, but here is some code showing how the data is originated. Object1 is a variable holding what should be an array called "someArray" in the model below and it is initialized using the adapter. Object2 was made like a traditional inline array: Object2 = [];
Model
define([], function () {
return [
function () {
function SomeObjectModel(someArray) {
this.someArray = someArray;
}
SomeObjectModel.prototype.serialize = function () {
return {
someArray: this.someArray,
};
};
SomeObjectModel.build = function (data) {
return new SomeObjectModel(data.someArray);
};
return SomeObjectModel;
},
];
});
Adapter
var serializedSomeObject = someObjectInstance.serialize();
return $http({
method: 'PUT',
url: 'someUrl.com/api',
data: serializedSomeObject,
}).then(function (response) {
return SomeObjectModel.build(response.data);
});
Edit 3: @CRice pointed out that this code can reproduce the Object1 instance which calls itself Array but does not behave as such:
var o = { 0: 'ABC', 1: 'IJK', 2: 'XYZ' };
Object.setPrototypeOf(o, Array.prototype);
console.log(o);
Edit 4: I don't know why serializing and then deserializing caused this but my solution is to iterate over the attributes of Object1 and just push them into a new array and call it a day. Here is my solution to convert Object1 to Object2:
var Object2 = [];
var attr = Object.getOwnPropertyNames(Object1);
for(var i=0; i<attr.length; i++){
Object2.push(Object1[attr[i]]);
}
console.log(Object2);
Thank you CRice for helping me reproduce it in the console!
Upvotes: 3
Views: 126
Reputation: 153
I found the cause. There is a module that deep clones complex objects so they can be restored in the future. Can you spot the error in this recursive loop?
function cloneObject(src) {
let target = {};
target.__proto__ = src.__proto__;
for (let prop in src) {
if (src.hasOwnProperty(prop)) {
// if the value is a nested object, recursively copy all it's properties
if (isObject(src[prop])) {
target[prop] = cloneObject(src[prop]);
} else {
target[prop] = src[prop];
}
}
}
return target;
}
When someArray goes through the cloneObject() function, it creates a new object called Array that does not retain true array properties but instead converts it into an object with proto='Array' (as @CRice pointed out) and nothing more. To fix this we really need to rework the cloneObject function to preserve arrays. This is the corrected deep copy cloneObject function I am now using:
function cloneObject(src) {
let target = {};
var isArray = Array.isArray(src);
if(isArray){
target = [];
} else {
target.__proto__ = src.__proto__;
}
for (let prop in src) {
if (src.hasOwnProperty(prop)) {
// if the value is a nested object, recursively copy all it's properties
if (isObject(src[prop])) {
var propertyValue = cloneObject(src[prop]);
} else {
var propertyValue = src[prop];
}
// if src was an array
if(isArray){
target.splice(prop, 0, propertyValue);
} else {
target[prop] = propertyValue;
}
}
}
return target;
}
Upvotes: 1