JavaScript: Reverse object destructuring / refactoring code to update object [duplicate] - javascript

This question already has answers here:
Is it possible to destructure onto an existing object? (Javascript ES6)
(16 answers)
Closed 4 years ago.
TLDR: How to use destructuring to speed up updating parts of one object based on another object of the same interface?
I would like to use the new ECMA2015 - 2017 JavaScript to refactor my code.
For simplicity let's say I have an object, looking like this:
export interface myObj {
name: string;
id: number;
description: string;
someBool: boolean;
anotherBool: boolean;
}
(Export interface works, because I'm working in Angular using Typescript).
Here is the old es5 function:
updateMyObj = function(oldObj, newObj) {
var someBoolSaved = oldObj.someBool;
var anotherBoolSaved = oldObj.anotherBool;
oldObj = newObj;
oldObj.someBool = someBoolSaved;
oldObj.anotherBool = anotherBoolSaved;
}
As you can see the function should update some parts of the oldObj, but also keep some parts.
Here is my attempt at refactoring the code:
updateObj(oldObj, newObj) {
let {someBool, anotherBool} = oldObj;
oldObj = newObj;
// Here is the part where I don't know how to proceed.
}
But I don't know, how I can now assign the oldObj's bools to the saved bools.
An inelegant way would be
oldObj.someBool = someBool;
oldObj.anotherBool = anotherBool;
which in this example would be fine. But in my actual task, this costs many lines of code which is why I want to refactor.
I just can't figure out the right syntax.
My attempts at coding this look similar like this:
oldObj = {someBool, anotherBool}
but this doesn't work.

If you want to assign the destructured value to a property, you do that by specifying the property on the right-hand side of a : in the destructuring.
For instance: The following assigns newObj.a and newObj.c to oldObj.a and oldObj.c:
({a: oldObj.a, c: oldObj.c} = newObj);
// ^^^^^^^^-----^^^^^^^^---- destinations for a and c
(The () are necessary because otherwise the { at the beginning looks like the beginning of a block. If we were already in an expression context, you wouldn't need them.)
const oldObj = {a: "old a", b: "old b"};
const newObj = {a: "new a", c: "new c"};
({a: oldObj.a, c: oldObj.c} = newObj);
console.log(oldObj);
As Bergi points out in a comment on the question, it doesn't really buy you much over
oldObj.a = newObj.a;
oldObj.c = newObj.c;
That's one reason various pick notations have kicked around (that one's seen activity quite recently). If that proposal were accepted (and it doesn't even have a champion yet, so don't hold your breath), you'd have oldObj.{a, c} = newObj;.

const newObj = { ...oldObject, someBool: true, anotherBool: false};
Edit:
Is updating the old object really what you want to do?
In your original code you mutate oldObj as a side-effect of the function. This is generally regarded as bad practise. The following is an example of a pure function with no side effects.
const firstObj = { name: 'hi', someBool: true, anotherBool: true };
const secondObj = { name: 'bye', someBool: false };
const thirdObj = mergeObjects(firstObj, secondObj);
// work with thirdObj from now on
function mergeObjects(firstObj, secondObj): myObj {
return {
...secondObj,
firstObj.someBool,
firstObj.anotherBool
}
}

Related

JS how to deep copy two objects [duplicate]

This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 1 year ago.
I have an obj1:
const obj1 = { val: {v: 0}}
I'm trying to deep copy it by doing:
const obj2 = {...obj1}
However the {v: 0} is still not copied, how would I do this>
I guess this is the easiest way, but a bit clumsy
const obj2 = JSON.parse(JSON.stringify(obj1))
Otherwise you need to write a recursive cloning function or use some library - just search for cloneDeep, copyDeep or smth along these lines.
You can use lodash-es package for this. It provides the function cloneDeep() that will recursively clone values.
Here is the package: https://www.npmjs.com/package/lodash-es
Here are the Docs: https://lodash.com/docs/4.17.15#cloneDeep
import { cloneDeep as _cloneDeep } from 'lodash-es';
const obj2 = _cloneDeep(obj1);
You are doing it how you should (with the spread operator). The spread operator will copy everything just fine.
const obj1 = { val: {v: 0} };
const obj2 = { ...obj1 };
console.log(obj2);
const obj1 = {
test: 1,
test2: {
t: 1,
f: () => console.log('hello')
}
};
const obj2 = { ...obj1 };
console.log( obj2 );
console.log( obj2.test2.f() );
If you were to (at any time) do a JSON.stringify on the object, you will lost any methods/functions or other things that don't translate to the json scheme so it's best to avoid doing that. Object destructuring is what you want.
I don't have enough rep to even flag but this is a cleeeeeear duplicate of a lot of questions
even googling the title would have been easier for you
anyway I use a module called rfdc that does just that, and lodash has a function to do it to
otherwise you can loop through your src object with a recursive function that adds fields to your dest object

TypeScript Dynamically or Programmatically Chain Functions

TypeScript function chaining, but I want to programmatically chain them.
Example class: chain.ts
class MyChain {
value: number = 0;
constructor() {
this.value = 0;
}
sum(args: number[]) {
this.value = args.reduce((s, c) => s + c, 0);
return this;
}
add(v: number) {
this.value = this.value + v;
return this;
}
subtract(v: number) {
this.value = this.value - v;
return this;
}
}
const mc = new MyChain();
console.log(mc.sum([1, 2, 3, 4]).subtract(5).value);
I see the number 5 on the console.
Now, I'm still fairly new to JavaScript and TypeScript, so I figured out that the function within this class is actually an element of an array of the instance of the class. Hence, I can do this:
console.log(mc["sum"]([1, 2, 3, 4]).value);
This indeed returns the number 10.
Now, I'm confused as to how I'd chain this programmatically. For example (this is obviously not what I would want to do anyway and shows my boneheaded lack of understanding of JavaScript:
console.log(mc["sum"]([1, 2, 3, 4]).mc["subtract"](5).value);
Error:
Property 'mc' does not exist on type 'MyChain'.ts(2339)
Okay, in all honesty, I kind of intuitively knew that wasn't going to work. However, thinking about it, how would I go about accessing the elements of a multidimensional array in just about any reasonable language?
console.log(mc["sum"]([1, 2, 3, 4])["subtract"](5).value);
Bingo. This does the trick. But, this isn't really the solution I need. What I need is something like this:
interface IChainObject {
action: string;
operand: number | number[];
}
const chainObj: IChainObject[] = [
{ action: "sum", operand: [1, 2, 3, 4] },
{ action: "subtract", operand: 5 },
];
And, to start, I'd like to try this:
console.log(mc[chainObj[0].action](chainObj[0].operand).value);
And consequently, generating a mechanism that would ultimately build something like this:
console.log(
mc[chainObj[0].action](chainObj[0].operand)[chainObj[1].action](
chainObj[1].operand
).value
);
Hence, it seems to me that what I want is some way to generate this:
[chainObj[0].action](chainObj[0].operand)[chainObj[1].action](chainObj[1].operand)
from this, with my chain object having one or many action/operand object sets:
const chainObj: IChainObject[] = [
{ action: "sum", operand: [1, 2, 3, 4] },
{ action: "subtract", operand: 5 },
];
Now, this is where my brain more or less shuts down. I am thinking that I need to generate a chain of string values, but they'll just be strings and won't really work as array indexes into the function as I want.
Why do I want to do this? Ultimately, I want to build a complex Yup schema object from a JSON object. I found this excellent post, but my core issue is I don't really understand how this code works.
At this point, I am able to parse out the way Vijay was able to solve his issue and mimic it, in a way. Here's working code for my example:
const mc = new MyChain();
interface IChainObject {
action: string;
operand: number | number[];
}
const chainObj: IChainObject[] = [
{ action: "sum", operand: [1, 2, 3, 4, 5] },
{ action: "subtract", operand: 5 },
];
let myChain = {};
chainObj.forEach((o) => {
myChain = mc[o.action](o.operand);
});
console.log("myChain is", myChain["value"]);
Results in: myChain is 10
You're probably asking yourself, "What's your problem Dan?. You seem to have a solution in hand now." Yes, I guess I do, but I don't understand it. I'm basically copying and pasting code, marginally understanding it, and making changes that make it work.
My basic issue is I don't understand how this line of code works: myChain = mc[o.action](o.operand);
I get the general gist that it's calling the function based on the action and providing the data to the function via the operand. I'm a copy and paste code monkey. I want to be more than a monkey. Maybe a baboon or even ape. Hence, I want to understand what I've done. What doesn't make sense to me is how it's chaining it.
I thought maybe the secret was in the forEach function, but that doesn't seem to be it. Here is a simple test:
let p = 0;
const x = [1, 2, 3, 4];
x.forEach((y) => {
p = y;
});
console.log("p is", p); p is 4
What is the secret JavaScript magic that is happening under the hood that makes the myChain = mc[o.action](o.operand); code actually chain my functions together rather than simply work one and the work the other. I'm just not seeing it.
Let's start from the first misunderstanding I can find:
Now, I'm still fairly new to JavaScript and TypeScript, so I figured out that the function within this class is actually an element of an array of the instance of the class.
This is not the case. Square brackets in Javascript are used for all property lookups, not just array indexing. x.foo is actually equivalent to x["foo"], and the same syntax works for arrays since arrays are just objects. Classes in Javascript are just objects that have a prototype property, which is itself an object. It contains a list of default attributes, and if you instantiate a class and look up a property that isn't in the object, it'll search for it in the prototype. So, looking at the code:
mc["sum"]([1, 2, 3])
It searches for a "sum" property in mc, and can't find any since you haven't defined one, so it searches in the prototype of MyChain, and finds the mc method. Thus, mc["sum"] is the sum method of mc. Now, this code:
console.log(mc["sum"]([1, 2, 3, 4]).mc["subtract"](5).value);
doesn't work, and it looks very off for a reason. mc["sum"]([1, 2, 3, 4]) returns mc, so why would you have to access the mc property (not that the mc property even exists)? That's why your second example, the one that calls subtract directly, works:
console.log(mc["sum"]([1, 2, 3, 4])["subtract"](5).value);
Now, let's look at the working code:
const mc = new MyChain();
interface IChainObject {
action: string;
operand: number | number[];
}
const chainObj: IChainObject[] = [
{ action: "sum", operand: [1, 2, 3, 4, 5] },
{ action: "subtract", operand: 5 },
];
let myChain = {};
chainObj.forEach((o) => {
myChain = mc[o.action](o.operand);
});
console.log("myChain is", myChain["value"]);
You actually don't need a lot of this code. It can be simplified down to:
const mc = new MyChain();
interface IChainObject {
action: keyof MyChain;
operand: number | number[];
}
const chainObj: IChainObject[] = [
{ action: "sum", operand: [1, 2, 3, 4, 5] },
{ action: "subtract", operand: 5 },
];
chainObj.forEach((o) => {
// bypass typescript type checking with cast
(mc[o.action] as Function)(o.operand);
});
console.log("myChain is", mc.value);
Essentially, the forEach loops through the elements in chainObj in order. The element's value is stored in the variable o. mc[o.action] takes the method name stored in o.action, and accesses it using square brackets. This is basically looking up the method. Then, the method is called with (o.operand) (in Javascript functions are just values, and you can call any value like a function, but if it's not a function it'll error). mc then modifies itself, and you move on to the next loop. If we insert a debugger statement in the function then break on the first loop, we can inspect the variables:
As you can see, the value starts off at 0, o.action is "sum", and mc[o.action] is the sum method. We can then call the sum method with o.operand, which adds the elements up and sets the value to 15. Then, in the second loop:
mc[o.action] is the subtract method, and we call it with o.operand, which is 5, lowering the value to 10.
Most things in Javascript, like classes are basically just objects.1
What that means is that attributes, or in this case - functions, can be accessed via the dot notation or bracket notation.
Lets look at an example that might assist the explanation:
class MyClass {
myFunction(x) {
console.log(x);
}
}
const x = new MyClass();
// attribute accessed via the dot notation
x.myFunction("Hello World!");
// attribute accessed via the bracket notation and a string
x['myFunction']("Hello World, again!");
// attribute accessed via a variable that is a string
const functionName = 'myFunction';
x[functionName]("Well uh, Hello World again?");
// attribute accessed via a variable that is a string, and passing in an argument
const argument = "This is " + "an argument";
x[functionName](argument);
To illustrate the point further:
class MyClass {
myFunction(x) {
console.log(x);
}
}
const x = new MyClass();
console.log(x.myFunction) // returns a function
console.log(x["myFunction"]) // returns a function
// executing the function
x.myFunction("Method One");
x["myFunction"]("Method Two")
We can see that the returned function can be called.
So let's get back to your example
chainObj.forEach((o) => {
myChain = mc[o.action](o.operand);
});
o.action is the function name
o.operand is the argument
Therefore, what is roughly translates to is:
chainObj.forEach((o) => {
myChain = mc[functionName](arugment);
});
just like our previous examples.
1 "classes are basically just objects"
There are so many pieces of this; I'm just going to focus on "what's the secret that makes the forEach() code work?"
The "secret" is that instances of MyChain have a property named value that gets updated after each method is called. The code with forEach() is not really chaining calls together; it just operates on the original MyChain variable named mc each time.
Since all the methods of MyChain that update this.value also return this, it happens not to matter whether you really chain calls (operate on the return value of each method call):
const chaining = new MyChain();
console.log(chaining.add(3).subtract(1).value); // 2
or if you just call methods on the original object in succession:
const notChaining = new MyChain();
notChaining.add(3);
notChaining.subtract(1);
console.log(notChaining.value) // 2
If you want there to be a difference between those, you can show it by making two versions of MyChain; one that only works via chaining, and one that only works in succession.
The following requires chaining because it never updates the original object and method calls return new objects with the results of the method call:
class RealChain {
constructor(public value: number = 0) { }
sum(args: number[]) {
return new RealChain(args.reduce((s, c) => s + c, 0));
}
add(v: number) {
return new RealChain(this.value + v);
}
subtract(v: number) {
return new RealChain(this.value - v);
}
}
const realChaining = new RealChain();
console.log(realChaining.add(3).subtract(1).value); // 2
const notRealChaining = new RealChain();
notRealChaining.add(3);
notRealChaining.subtract(1);
console.log(notRealChaining.value) // 0
and the following prohibits chaining, because it only updates the original object and its methods don't return anything:
class NotChain {
value: number = 0;
constructor() {
this.value = 0;
}
sum(args: number[]) {
this.value = args.reduce((s, c) => s + c, 0);
}
add(v: number) {
this.value = this.value + v;
}
subtract(v: number) {
this.value = this.value - v;
}
}
const realNotChaining = new NotChain();
realNotChaining.add(3);
realNotChaining.subtract(1);
console.log(realNotChaining.value) // 2
const badNotChaining = new NotChain();
console.log(badNotChaining.add(3).subtract(1).value); // error!
// badNotChaining.add(3) is undefined so you can't call subtract() on it
The code with forEach() would only work with NotChain instances and not with RealChain instances.
If you want a programmatic loop-like thing that actually works with chaining and not calling methods on an original object, you should probably use reduce() instead of forEach():
const realChainReduced = chainObj.reduce(
(mc, o) => mc[o.action](o.operand),
new RealChain() // or MyChain, doesn't matter
);
console.log("realChainReduced is", realChainReduced.value); // 10
Note that I didn't cover any of the other parts, including TypeScript specifics (the typings used here give some compiler errors), so be warned.
Playground link to code

Unlike Java, does an assignment operator in Javascript mean that all operations on the LHS are reflected for the RHS?

This is in continuation to the answer posted for the question "Convert JavaScript dot notation object to nested object".
The code works like a charm but I'm unable to wrap my head around how!! So a few days later + a situation where my console.logs actually exceed my lines of code :P.. Here's my question:
Below code for JavaScript function:
function deepen(o) {
var oo = {}, t, parts, part;
for (var k in o) {
t = oo;
parts = k.split('.');
var key = parts.pop();
while (parts.length) {
part = parts.shift();
t = t[part] = t[part] || {};
}
t[key] = o[k]
}
return oo;
}
console.log(
deepen({ 'ab.cd.e' : 'foo', 'ab.cd.f' : 'bar', 'ab.g' : 'foo2' })
);
It deepens a JSON object from :
{
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2' }
Into a nested object :
{ab: {cd: {e:'foo', f:'bar'}, g:'foo2'}}
I get the part where for each key value pair, the logic pops the last element post splitting into an array by ".".
That becomes the key.
What I'm not understanding is the below.
1) The function is returning 'oo' but the operations are all on 't'. The only relationship is that t is being assigned the'empty object' "oo" at the beginning of every iteration on the flat JSON.
2) after the "while (parts.length)" loop, oo miraculously has the nested structure whereas t has one level below it. if oo is assigned to t, how is that possible?
3) I don't see the function being called recursively. How is 00 getting nested beyond the first element of the flat JSON?
I'll first redefine the function with some better names, this way explanation is a lot easier to do.
function deepen(object) {
var nestedObject = {}, cursor, nestingPath, nodeKey;
for (var dotKey in object) {
cursor = nestedObject;
nestingPath = dotKey.split('.');
var leafKey = nestingPath.pop();
while (nestingPath.length) {
nodeKey = nestingPath.shift();
cursor = cursor[nodeKey] = cursor[nodeKey] || {};
}
cursor[leafKey] = object[dotKey];
}
return nestedObject;
}
My guess is that don't entirely know how the while loop functions. Important to know is that when two variables refer to the same object both change when you change one. They are the same object, but you've chosen to have two handles.
Let me provide an example:
object = {};
cursor = object;
cursor.foo = "bar";
object; //=> {foo: "bar"}
cursor; //=> {foo: "bar"}
cursor.a = {};
object; //=> {foo: "bar", a: {}}
cursor; //=> {foo: "bar", a: {}}
cursor = cursor.a;
object; //=> {foo: "bar", a: {}}
cursor; //=> {} <- this is ^
cursor.b = "c";
object; //=> {foo: "bar", a: {b: "c"}}
cursor; //=> {b: "c"}
The while loop is mostly based upon this principal. It's not easy to explain, but I hope the above clarifies things.
Another thing that might be confusing is the line:
cursor = cursor[nodeKey] = cursor[nodeKey] || {};
// read as
cursor = (cursor[nodeKey] = (cursor[nodeKey] || {}));
This could also be written as:
if (!cursor[nodeKey]) cursor[nodeKey] = {};
cursor = cursor[nodeKey];
This assigns a new object to the dynamic nodeKey property if the property isn't there (falsy). Then cursor is assigned to the nested object within, similar to my example above cursor = cursor.a.
First, you're not working with JSON, but a JS object. Most of the time, you should see object as HashMap<String, HashMap<String, ...>> ad infinitum, if you need a Java analogy.
Your questions:
t = oo means they both refer to the same instance created at the start of the function. Why do you use a second variable?
t = t[part] You literally assign entry of t to t
I didn't test the code, but I'm pretty sure it's buggy. Test what happens with object that have multiple names in top level, eg. {'a.b':1, 'b.a':1}. You don't need recursion though, you could use stack instead.
Regarding your code:
Use descriptive names and comments, especially when asking question where other people need to understand your code
Do not define all variables at the beginning of the function. That old habit comes from the dawn of C language and needs to die
for (var k in o) is not a recommended approach to iterate over object entries. Use Object.entries
There is no need to pop from parts array, it is reset every iteration. for(const part of parts) would work just as well

How to rename Object Key when you have been given the function in JS

So I have been given a question below...
function translateKey(student, keyToChange, translation) { }
/*
This function will take an object representing a student's data, a key that needs changing, and its English translation.
E.g.
const student = {
prénom: 'Carla',
surname: 'Bruni',
job: 'Artist'
}
const keyToChange = 'prénom'
const translation = 'firstName'
It returns a **new object** with the key successfully translated into English.
E.g.
{
firstName: 'Carla',
surname: 'Bruni,
job: 'Artist'
}
*/
I have started off with ...
function translateKey(student, keyToChange, translation) {
const translated = {keyToChange : translation};
const newObject = {};
// not really sure where to go from here, help please!
Since your objectif is to rename an object key you can start looking at this question who has very good answers.
And you can take the answer of ChaosPandion as example and maybe do it like this :
function translateKey(student, keyToChange, translation) {
var newObject = Object.assign({}, student);
// Do nothing if the names are the same
if (keyToChange === translation) {
return newObject;
}
// Check for the old property name to avoid a ReferenceError in strict mode.
if (newObject.hasOwnProperty(keyToChange)) {
newObject[translation] = newObject[keyToChange];
delete newObject[keyToChange];
}
return newObject;
}
If it's an exercice you had to do for a class please read the other question I linked to see others ways and try to properly understand the solution, for example why it's important to check hasOwnProperty or why we use === instead of simply ==.
if (old_key !== new_key) {
Object.defineProperty(o, new_key,
Object.getOwnPropertyDescriptor(o, old_key));
delete o[old_key];
}
From this answer:
https://stackoverflow.com/a/14592469/9504351

ES6 - Possible to destructure from object into another object property? [duplicate]

This question already has answers here:
Object destructuring without var, let or const
(4 answers)
Closed 4 years ago.
So I'm trying to figure out if there's any simple ES6 syntax to do the following:
If I have an object
const config = { foo: null, bar: null }
And I want to assign the values of those properties from another object such as:
const source = { hello: "hello", world: "world", another: "lorem", onemore: "ipsum" }
I want to do something like the following but it doesn't work
{ hello:config.foo, world:config.bar } = source
I know I can do something very close like:
{ hello:foo, world:bar } = source
But this creates new variables foo and bar, whereas I want to assign to existing properties on another object. I'm just curious if there's an ES6 shorthand for this; I don't need help doing this with traditional code, I know there are a dozen ways and I already know most of them.
You're just missing brackets () around the statement.
const config = {};
const source = { hello: "hello", world: "world", another: "lorem", onemore: "ipsum" };
({hello: config.foo, world: config.bar} = source);
console.log(config);

Categories