How to use Object.assign() in Observable?

Hi Community!

I haven’t hit on the solution for this, but trying the MDN documentation for Object.assign() isn’t returning the expected outputs.

Input this as separate cells:

target = [{ a: 1, b: 2 }]
source = [{ b: 4, c: 5 }]

Then adding:

Object.assign(target, source)

… Transforms the first array (expected) but loses first array values (unexpected). According to Mozilla, the expected output should be Object { a: 1, b: 4, c: 5 }
``

object-assign-different-than-expected

I tried messing around with New and mutable but to no avail. How should I go about merging together two objects in Observable?

The target and source are meant to be an objects instead of the array with the object?

2 Likes

To add to what @a10k said, either you can Object.assign individual objects:

{
  const target = {a: 1, b: 2};
  const source = {b: 4, c: 5};
  return Object.assign(target, source);
}

Or you can use array.map to do it for multiple parallel arrays of objects:

{
  const targets = [{a: 1, b: 2}];
  const sources = [{b: 4, c: 5}];
  return targets.map((target, i) => Object.assign(target, sources[i]));
}

I would be careful not to Object.assign a target that is defined in another cell, as this is an example of cross-cell mutation which is an antipattern in Observable. When in one cell you mutate a value defined in another cell, you’re going against one-way dataflow and it typically means the behavior of your notebook will be nondeterministic (behave differently on reload for different viewers). To avoid that here I’ve combined the definition of target and its mutation by Object.assign into a single cell. That way the rest of the notebook only sees target after it’s been mutated.

1 Like

Adding to what Mike wrote, you can also merge your objects into new objects (note the first argument to .assign here is a new “blank” object):

return targets.map((target, i) => Object.assign({}, target, sources[i]));

or via destructuring:

return targets.map((target, i) => ({...target, ...sources[i]}));

This avoids mutation of the target objects.

2 Likes

Thank you both! This helps. And good catch @Alok!

I understand now that because the cells still realize their being evaluated together), a similar attempt reference data pre-loaded as individual cells will still fail, e.g.:

{
  const function_target = target;
  const function_source = source;
  return Object.assign(function_target, function_source)
}

… .will still return Object {b: 4, c: 5} :frowning:

… I am trying to load in CSV data then manipulate it. Seems like for this function I have no choice but just to call it back in anew? No ‘shallow copy’ of a fetched dataset?

Edit:

Thanks @mootari! - I think this answers my question on the ‘shallow’ copy. Seems I can’t just enter return targets.map((target, i) => ({...target, ...sources[i]})); on its own within a separate cell though… it must still be part of the ‘combined’ expression

Further edit:

I didn’t highlight this earlier, but Alok’s tip was very helpful - as when changing the code to return an object resulted in Object.assign() returning the expected result! :slight_smile:

Everything but the simplest values (so-called “scalar values”) get passed by reference. When you assign the object referenced by target to the variable function_target, it’s still the exact same object.

If an object only contains other objects, arrays or scalar values, you can often use this one-liner to create a deep copy:

const my_deep_copy = JSON.parse(JSON.stringify(my_object));

This serializes my_object into a JSON string, and then parses the string again to recreate the object.

I’m not sure what you mean by that, but my best guess is that you have a syntax error? Assuming that sources and targets are both separate cells, you can create a new cell my_result with:

my_result = targets.map((target, i) => ({...target, ...sources[i]}))
1 Like

Thanks @mootari, I just re-published the notebook trying out these approaches. I did not an unexpected token error for having tried return (which I guess is implicit in Observable)

The trouble that I am having is that I wish to construct rather lengthy CSV or JSON-formatted data objects, separately, then run a function that combines them. I don’t wish to mutate the original object. I am trying to figure out how to accomplish that… but I still not sure that I’m over the line.

I see how combining variable definition in a single cell is needed, but I am not yet sure if I understand how I can do that without repeating all the data definitions. That is, if I have already gone through all the trouble of writing ‘list a’, which I wish to merge with ‘list b’, I’d prefer not to have to spell out all of what ‘list A’ is again.

Your ‘deep copy’ approach might help. I’ll give it a whirl now.

EDIT:
I’ve changed the ‘solution’ tag on this so many times: but I think it’s @mootari’s ‘deep copy’ answer for the win :1st_place_medal:

{
  const targets_deep_copy = JSON.parse(JSON.stringify(target));
  const sources_deep_copy = JSON.parse(JSON.stringify(source));
  return targets_deep_copy.map((targets_deep_copy, i) => Object.assign(targets_deep_copy, sources_deep_copy[i]));
}

Thanks everyone!

Which notebook? I don’t see a link anywhere?

first post, last line :wink:

Ah, mistook that for an MDN link :sweat_smile:

1 Like

Hey @aaronkyle, I’m a bit late to this party, but then again, I might still learn from it.

For one, what exactly are you trying to achieve?

  1. the proper use of Object.assign()?
  2. merging two objects into a single one?
  3. zipping together two equal length arrays of objects into a single list of merged objects?
  4. something else (if so, what exactly)?

To what extent does your goal relate to what they call in Functional Programming a pure function:

  1. has input parameters;
  2. does not use or set stateful values (outside of itself);
  3. return based on input; always returns the same results when given the same input; and
  4. has no side effects outside itself (like writing to screen, database, file, network or output channel).

This avoids the anti-pattern Mike warns for: Object.assign()’s primary goal is to create the side effect of changing existing objects (target in your case).

Also, do you really need a deep copy of the object(s)? My experience is that I never needed a deep copy when I use the pure approach. Simply create new objects from existing ones. The ‘garbage’ this leaves behind will be collected by the system automatically when needed.

So, I’d simply do ({...target, ...source}) to merge the two objects.

Also see Object.assign()’s ‘impurity’ / Martien van Steenbergen / Observable

Just my €.02.

1 Like

Hi @Martien and thanks for the help! And you’re never late to the party! :smile:

My specific use case is this: A client and I have agreed to a set of actions (original list). After some time, I am reviewing the status of completed actions. I wish to call in the original list, and then to that list I would like to append my review. Here’s an example:

Original task list:

ID Task Target Completion Date Responsible Party
1 Select wall materials 30-Aug-21 Anna
2 Select wall colors 10-Aug-21 Anna
3 Buy wall materials 15-Aug-21 Aaron
4 Buy wall paints 15-Aug-21 Aaron

Status review:

ID Status Revised Competion Date Notes
1 Complete n/a
2 Complete n/a
3 Complete n/a
4 Pending 20-Aug-21 Paint wasn’t available.

The original list informs some other bits of information, so I don’t wish to mutate it. Rather, I wish to reference it, append to it my review, and return a ‘full’ recounting of the situation as an update. I.e. I would like to return this:

ID Task Target Completion Date Responsible Party Status Revised Competion Date Notes
1 Select wall materials 30-Aug-21 Anna Complete n/a
2 Select wall colors 10-Aug-21 Anna Complete n/a
3 Buy wall materials 15-Aug-21 Aaron Complete n/a
4 Buy wall paints 15-Aug-21 Aaron Pending 20-Aug-21 Paint wasn’t available.

To respond to your questions:

For one, what exactly are you trying to achieve?

  1. the proper use of Object.assign()?

Yes, I always would like to better understand the proper use of tools. Generally, I read through the Internet trying to figure out what I am doing and how it can be accomplished with JavaScript. Learning how to ask questions and what to look for remains an ongoing struggle.

  1. merging two objects into a single one?

Yes, this is absolutely what I am after. From reading I assumed I was on the right path with Object.assign()

  1. zipping together two equal length arrays of objects into a single list of merged objects?

Yes.

  1. something else (if so, what exactly)?

Nope, should be covered by the 3 points above!

To what extent does your goal relate to what they call in Functional Programming a pure function:

I have no idea what a ‘pure’ function even means! Just the other day, @chrispahm introduced me to the concept of a function! (I have no computer science background at all…)

  1. has input parameters;

I don’t know that i really understand what ‘parameter’ means :frowning:

  1. does not use or set stateful values (outside of itself);

I don’t know what a ‘stateful value’ is :frowning:

  1. return based on input; always returns the same results when given the same input; and

Yes! Always I would like to return the same results when given the same inputs

  1. has no side effects outside itself (like writing to screen, database, file, network or output channel).

I am not sure? I do intend to render the object into an HTML table, but for now I am just looking to create the object.

This avoids the anti-pattern Mike warns for: Object.assign()’s primary goal is to create the side effect of changing existing objects (target in your case).

Your further elaboration :

Object.assign() modifies target in place: side effect that can reduce your code’s evolvability (legibility, understandability, maintainability, changeability) and can create all kinds of erratic behavior .

Oh no! Then I don’t want Object.assign() after all! :scream:

Also, do you really need a deep copy of the object(s)?

I don’t wish to mutate the source objects at all. I just want to produce an output that merges the original and the added ‘review’.

My experience is that I never needed a deep copy when I use the pure approach. Simply create new objects from existing ones. The ‘garbage’ this leaves behind will be collected by the system automatically when needed.

:slight_smile: One man’s trash…

So, I’d simply do ({...target, ...source}) to merge the two objects.

Cool! Can you elaborate on how this is achieved?

Also see Object.assign()’s ‘impurity’ / Martien van Steenbergen / Observable

Awesome! Thanks!

Just my €.02.

:slight_smile: Thanks! In USA dollars, I think that’s almost 3 cents! :stuck_out_tongue:

Hi Aaron, please see Joining two arrays at the hip « Martien « Observable if this meets your needs.

Regarding your question “elaborate on how this is achieved?”. Well,

  1. the spread operator ... in front of an object ‘serializes’ or unpacks it; so ...target, ...source unpacks both objects and leaves the result dangling in the air;
  2. next, ({…}) packs the unpacked results into a new object;
  3. There is no step three.

It is a shallow copy, shallow meaning the opposite of deep. Deep would traverse and copy unto and including all the edges of a potentially nested object.

BTW, would Trello be a tool for you to manage your tasks?

Please let me know if I can be of further assistance.

BTW Kudos to you for all your notebooks created, even without “computer science background at all”.

1 Like

Wow @Martien! You’ve really gone above and beyond! This is very helpful. Thank you for equipping me / all viewers with language and examples that bridge concepts across platforms !
Thank you also for your kind words on the notebooks. Really I view these all as various renditions of other peoples’ stuff, which at some point I’ve worked through to learn or explore something. During this process, I have this inner dialog going where I try to ‘get the words right’ while I conceptually think through how everything relates. Sometimes (often) I can be far off! And I tend to repeatedly revisit a problem before it clicks. I appreciate the interactions on this forum as people help each other (I feel especially blessed in this regard).

I’ll look into Trello. I generally don’t use task managers. Too busy just doing things :stuck_out_tongue: In this case, I am still trying to learn the basics of JS as a tool for structuring and managing data.

Reminds me of the woodcutter not sharpening his axe because of having no time; he still needs to cut down the whole forest… :wink:

2 Likes