I am trying to create a function that will copy all properties from a source object and paste them to a destination object. I want to create a deep copy, so I am not using object.assign().
Here is my code:
var obj1 = {
arr: ['a','b','c','d'], // >= 4 elements, all should be truthy
obj: {a:1,b:2,c:3,d:4}, // >= 4 values, all should be truthy
halfTruthyArr: [null,'b',null,'d'], // >= 4 elements, half should be falsy
halfTruthyObj: {a:1,b:null,c:3,d:null}, // >= 4 values, half should be falsy
string: 'This is a string.',
reverseString: function (string) {
if (typeof string === 'string') return string.split('').reverse().join('');
}
};
var obj2 = {}
function extend(destination, source) {
destination = JSON.parse(JSON.stringify(source))
}
extend(obj2,obj1)
console.log(obj2)
obj2 is not changed using the function. I understand the function uses pass by reference, which is why the object is not reassigned. Is there a way around this so that the object I pass in as a parameter is edited?
The reverseString method in obj1 does not get copied using JSON.stringify. Why is this?
if you are setting the value of an object or array inside of function it is Pass by Value. So you need to pass object by reference or try this
function extend(source) {
return JSON.parse(JSON.stringify(source))
}
var obj2=extend(obj1);
console.log("ext",obj2)
or pass by reference. In this case you are only changing the property inside of the object, not assigning a new value to the whole object
function extend(source, destination) {
destination.result = JSON.parse(JSON.stringify(source));
}
var destination = { result: {} };
extend(obj1, destination);
var obj2=destination.result;
console.log("ext", obj2);
Related
The function takes an input path like a.b.c and should output a nested structure json like:
{
a: {
b: {
c: {}
}
}
}
The algorithm using iterative style is:
function stringToObj(path, obj) {
var parts = path.split(".");
var part;
while ((part = parts.shift())) {
if (typeof obj[part] != "object") obj[part] = {};
obj = obj[part]; // line 6
}
}
Current usage:
let result = {};
stringToObj("a.b.c", result);
console.log(result); // outputs the json
JsFiddle
The problem:
It relies mutating the obj parameter on line 6.
I would like to not rely upon passing the result object, and rather create one inside the function. Doing so results in different results. A desired example usage:
const result = stringToObj("a.b.c"); // result should be the json
Context:
The exercise is for learning purpose. Main objective is understanding why removing obj and rewriting the function as per follows doesn't work as expected:
function stringToObj(path) {
var obj = {};
var parts = path.split(".");
var part;
while ((part = parts.shift())) {
if (typeof obj[part] != "object") obj[part] = {};
obj = obj[part]; // line 6
}
return obj;
}
After splitting by .s, you can use reduceRight to start at the last property, c, while taking an initial value of an empty object. Inside the callback, use a computed property to return a new object containing the old object at the property being iterated over:
const stringToObj = str => str.split('.').reduceRight(
(lastObj, prop) => ({ [prop]: lastObj }), {}
);
console.log(stringToObj('a.b.c'));
If you're not familiar with it, reduceRight is like reduce, except that it iterates starting from the last element in the array and going backwards, instead of starting from the first element of the array and going forwards. On each iteration, the callback is called, where the first argument (here, lastObj) is the value returned from the last iteration, and the second argument is the current item of the array being iterated over.
You can also reverse the array of properties and use reduce instead of reduceRight, which might be easier to understand at a glance, but it's a bit less elegant:
const stringToObj = str => str.split('.').reverse().reduce(
(lastObj, prop) => ({ [prop]: lastObj }), {}
);
console.log(stringToObj('a.b.c'));
Also, don't mix var and let. If you're going to use ES2015 syntax - which you should - consider always using const, and only use let when you must reassign. Never use var, it has too many gotchas to be worth using in modern code.
I think I've seen how to create a JSON object without first preparing it. This is how i prepare it:
obj = {
0:{
type:{}
},
1:{},
2:{}
};
Now I think I can insert a value like: obj.0.type = "type0"; But I'd like to create it while using it: obj['0']['type'] = "Type0";.
Is it possible, or do I need to prepare it? I'd like to create it "on the fly"!
EDIT
I'd like to create JS object "On the fly".
var obj = {};
obj.test = "test"; //One "layer" works fine.
obj.test.test = "test" //Two "layers" do not work... why?
obj = {
0:{
type:{}
},
1:{},
2:{}
};
Now i think i can insert value like: obj.0.type = "type0";
I guess you mean "assign" a value, not "insert". Anyway, no, you can't, at least not this way, because obj.0 is invalid syntax.
But I'd like to create it while using it: obj['0']['type'] = "Type0";
That's fine. But you need to understand you are overwriting the existing value of obj[0][type], which is an empty object ({}), with the string Type0. To put it another way, there is no requirement to provide an initialized value for a property such as type in order to assign to it. So the following would have worked equally well:
obj = {
0:{},
1:{},
2:{}
};
Now let's consider your second case:
var obj = {};
obj.test = "test"; //One "layer" works fine.
obj.test.test = "test" //Two "layers" do not work... why?
Think closely about what is happening. You are creating an empty obj. You can assign to any property on that object, without initializing that property. That is why the assignment to obj.test works. Then in your second assignment, you are attempting to set the test property of obj.test, which you just set to the string "test". Actually, this will work--because strings are objects that you can set properties on. But that's probably not what you want to do. You probably mean to say the previous, string value of obj.test is to be replaced by an object with its own property "test". To do that, you could either say
obj.test = { test: "test" };
Or
obj.test = {};
obj.test.test = "test";
You are creating a plain object in JavaScript and you need to define any internal attribute before using it.
So if you want to set to "Type0" an attribute type, inside an attribute 0 of an object obj, you cannot simply:
obj['0']['type'] = "Type0";
You get a "reference error". You need to initialize the object before using it:
var obj = {
0: {
type: ""
}
};
obj['0']['type'] = "Type0";
console.log(obj['0']['type']);
You could create your own function that takes key as string and value and creates and returns nested object. I used . as separator for object keys.
function create(key, value) {
var obj = {};
var ar = key.split('.');
ar.reduce(function(a, b, i) {
return (i != (ar.length - 1)) ? a[b] = {} : a[b] = value
}, obj)
return obj;
}
console.log(create('0.type', 'type0'))
console.log(create('lorem.ipsum.123', 'someValue'))
Is it necessary to create nested objects before using it?
Yes it is, at least the parent object must exist.
Example:
var object = {};
// need to assign object[0]['prop'] = 42;
create the first property with default
object[0] = object[0] || {};
then assign value
object[0]['prop'] = 42;
var object = {};
object[0] = object[0] || {};
object[0]['prop'] = 42;
console.log(object);
Create object with property names as array
function setValue(object, keys, value) {
var last = keys.pop();
keys.reduce(function (o, k) {
return o[k] = o[k] || {};
}, object)[last] = value;
}
var object = {};
setValue(object, [0, 'prop'], 42);
console.log(object);
I have a question regarding Chai library for unit tests. I noticed a statement saying:
equal: Asserts that the target is strictly (===) equal to the given value.
eql: Asserts that the target is deeply equal to value.
I'm confused about what the difference is between strictly and deeply.
Strictly equal (or ===) means that your are comparing exactly the same object to itself:
var myObj = {
testProperty: 'testValue'
};
var anotherReference = myObj;
expect(myObj).to.equal(anotherReference); // The same object, only referenced by another variable
expect(myObj).to.not.equal({testProperty: 'testValue'}); // Even though it has the same property and value, it is not exactly the same object
Deeply Equal on the other hand means that every property of the compared objects (and possible deep linked objects) have the same value. So:
var myObject = {
testProperty: 'testValue',
deepObj: {
deepTestProperty: 'deepTestValue'
}
}
var anotherObject = {
testProperty: 'testValue',
deepObj: {
deepTestProperty: 'deepTestValue'
}
}
var myOtherReference = myObject;
expect(myObject).to.eql(anotherObject); // is true as all properties are the same, even the inner object (deep) one
expect(myObject).to.eql(myOtherReference) // is still also true for the same reason
here
equal is ===
checks if both object references or points to the exact same or identical object.
var obj = {
k1: 'v1'
};
var obj1 = obj
var obj2 = obj
here obj1 === obj2 (true)
and obj1 == obj2 (true)
eql: Asserts that the target is deeply equal to value.
number 2 ie. eql checks if both objects have the same value. (they could be different objects with the same values )
var obj1 = {
k1: 'v1'
}
var obj2 = {
k1: 'v1'
};
There are a few plugins that help you in terms of the above condition where you can simply use _.isEqual to check the object values:
UnderScore
Lodash
isDeepStrictEqual(object1, object2) Node
eg console.log(_.isEqual(obj1, obj2)); // true
I ran into this potential scenario that I posed to a few of my employees as a test question. I can think of a couple ways to solve this problem, but neither of them are very pretty. I was wondering what solutions might be best for this as well as any optimization tips. Here's the question:
Given some arbitrary string "mystr" in dot notation (e.g. mystr = "node1.node2.node3.node4") at any length, write a function called "expand" that will create each of these items as a new node layer in a js object. For the example above, it should output the following, given that my object name is "blah":
blah: { node1: { node2: { node3: { node4: {}}}}}
From the function call:
mystr = "node1.node2.node3.node4";
blah = {};
expand(blah,mystr);
Alternately, if easier, the function could be created to set a variable as a returned value:
mystr = "node1.node2.node3.node4";
blah = expand(mystr);
Extra credit: have an optional function parameter that will set the value of the last node. So, if I called my function "expand" and called it like so: expand(blah, mystr, "value"), the output should give the same as before but with node4 = "value" instead of {}.
In ES6 you can do it like this:
const expand = (str, defaultVal = {}) => {
return str.split('.').reduceRight((acc, currentVal) => {
return {
[currentVal]: acc
}
}, defaultVal)
}
const blah = expand('a.b.c.d', 'last value')
console.log(blah)
Here's a method that popped up in my mind. It splits the string on the dot notation, and then loops through the nodes to create objects inside of objects, using a 'shifting reference' (not sure if that's the right term though).
The object output within the function contains the full object being built throughout the function, but ref keeps a reference that shifts to deeper and deeper within output, as new sub-objects are created in the for-loop.
Finally, the last value is applied to the last given name.
function expand(str, value)
{
var items = mystr.split(".") // split on dot notation
var output = {} // prepare an empty object, to fill later
var ref = output // keep a reference of the new object
// loop through all nodes, except the last one
for(var i = 0; i < items.length - 1; i ++)
{
ref[items[i]] = {} // create a new element inside the reference
ref = ref[items[i]] // shift the reference to the newly created object
}
ref[items[items.length - 1]] = value // apply the final value
return output // return the full object
}
The object is then returned, so this notation can be used:
mystr = "node1.node2.node3.node4";
blah = expand(mystr, "lastvalue");
var obj = {a:{b:{c:"a"}}};
const path = "a.b.c".split(".");
while(path.length > 1){
obj = obj[path.shift()];
}
obj[path.shift()] = "a";
While I realize that an array, as a non-primitive data type, is handled by references in JavaScript, not by value, any particular element of that array could be a primitive data type, and I assume then that it is not assigned by reference.
I'd like to know how to get a reference to an individual element in an array so that I don't have to keep referring to the array and the index number while changing that element?
i.e.
var myElement=someArray[4]
myElement=5
//now someArray[4]=5
Am I misinterpreting various docs that imply but do not explicitly state that this is not the intended behavior?
You can make a copy of an array element, but you can't create a value that serves as an alias for an array property reference. That's also true for object properties; of course, array element references are object property references.
The closest you could get would be to create an object with a setter that used code to update your array. That would look something like:
var someArray = [ ... whatever ... ];
var obj = {
set element5(value) {
someArray[5] = value;
}
};
Then:
obj.element5 = 20;
would update someArray[5]. That is clearly not really an improvement over someArray[5] = 20.
edit — Now, note that if your array element is an object, then making a copy of the element means making a copy of the reference to the object. Thus:
var someArray = [ { foo: "hello world" } ];
var ref = someArray[0];
Then:
ref.foo = "Goodbye, cruel world!";
will update the "foo" property of the object referenced by someArray[0].
You can always pass around a closure to update this:
var myUpdater = function(x) {
someArray[4] = x;
}
myUpdater(5);
If you want read/write capabilities, box it:
var makeBox = function(arr, n) {
return {
read: function() { return arr[n]; },
write: function(x) { arr[n] = x; }
};
}
// and then:
var ptr = makeBox(someArray, 4);
ptr.read(); // original
ptr.write(newValue);
someArray[4]; // newValue