Shallow copy JavaScript object without references - javascript

How can I shallowly copy an JavaScript object and get rid of all non-primitive values (all references), while keeping all properties of the given object. Values of the properties might turn to null in this process.
Object.assign, lodash clone and the spread operator allow us to get a shallow copy of an object. However, despite the naming, the result object is not shallow [fact]. It has copied all references too, so the whole object tree is still accessible.
For a analytics solution, I need to get rid of anything deeper than one level.
How can I do it (libraries are also ok, ES6 is fine) without writing dozens of rules to deal with all the possible data types? Ideally, objects and array properties are not lost, but replaced with something, e.g. null or empty objects/arrays.
Example
const source = {
nr: 1,
str: 'ok',
obj: {
uhOh: 'kill me'
},
arr: ['well ok', { uhOh: 'uhOh' }],
}
// apply voodoo
const expected = {
nr: 1,
str: 'ok',
obj: {},
arr: [],
}
// This would also be an valid result:
const expected = {
nr: 1,
str: 'ok',
obj: null,
arr: null,
}

You could loop through the keys of the object using for...in. If the value is an object, set the key to null in expected, else set the value in expected to the value from source
const source = {
nr: 1,
str: 'ok',
obj: {
uhOh: 'kill me'
},
arr: ['well ok', {
uhOh: 'uhOh'
}],
}
const expected = {};
for (const key in source) {
if (typeof source[key] === 'object')
expected[key] = null
else
expected[key] = source[key]
}
console.log(expected)

This is not an answer in it's own right, but an addendum to the excellent answer by #adiga, this time using typescript and a type parameter:
private primitiveClone<T>(source: T): T {
const dto = Object.assign({}, source);
for (const key in dto) {
if (typeof dto[key] === 'object') {
dto[key] = null;
}
}
return dto;
}
usage
var simpleClone = primitiveClone(data);

Related

Merge function return into bbject: Spread vs Object.assign [duplicate]

I'm reading an introduction to Redux reducers (https://redux.js.org/introduction/three-principles) which contains the following example of a reducer:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}
It seems from its documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) that Object.assign() will 'merge together' all the objects passed into it. In this case, however, todo and {completed: true} are already objects, so I don't see the point of passing an empty object literal, {}, as the first argument to Object.assign(). Can anybody clarify this?
When you use Object.assign, the first object you give it will have all the rest of the objects merged into it. That is to say, the first object will be mutated.
If you want to avoid mutating the objects you're merging, it's helpful to pass in the empty object as the first parameter to prevent any of the component objects from changing.
Here's an example demonstrating the difference:
const obj1 = {
foo: "bar"
}
const obj2 = {
key: "value"
}
// Here, obj1 is the same after the Object.assign call
console.log(Object.assign({}, obj1, obj2));
console.log(obj1)
console.log(obj2)
console.log("\n\n")
// Note that after this call, obj1 holds both keys. So this will mutate it:
console.log(Object.assign(obj1, obj2));
console.log(obj1) // This is different now
console.log(obj2)
If you don't pass an empty object in, the original todo object will be modified. This may be what you want, but more often than not it isn't.
This is due to the way objects are all references, and are not cloned by default.
Short answer: Objects and Arrays are assignment by reference.
In this example, changing one will change the other, they are not immutable:
let x = {param:1}
const foo = (a) => {
a.param +=1;
console.log('response', x, a)
}
foo(x);
To fix that, we use Object.assign()
let x = {param:1}
const foo = (a) => {
let b = Object.assign({}, a);
b.param +=1;
console.log('response', b, x)
}
foo(x);

How to update/set the values of multiple Objects (with the same parameters) with the same values JavaScript/Typescript

What is the best way/how can I update two Objects with the same set of values?
The only method I know of, is by setting each property of each object concurrently. As per example below. Below I am using a method to populate the Objects, by passing the values as parameter in the method.
PLEASE NOTE: the individual parameter I pass in the method (populateIndividualDetails(individual: SelectedMemberIndividualData)) consists of many parameters I do not need, and is not in the format I desire. Hence the use of a method to assign the properties.
Additional Note: Both Objects I wish to populate have the same parameters, and is in the exact same format. The Objects have nested parameters.
Perhaps one could copy the 1st Object after it has been populated? 🤔
Example:
model = {
initials: '',
name: '',
address: {
streetName: '',
...
}
...
}
initialValues= {
initials: '',
name: '',
address: {
streetName: '',
...
}
...
}
populateIndividualDetails(individual: SelectedMemberIndividualData) {
this.model.initials = individual.initials;
this.initialValue.initials = individual.initials;
...
}
Rather than populating model and initialValues with empty key-value pairs, you could instead consider making an array of the properties which you want to be set in both the model and initialValues objects. Inside of populateIndividualDetails() you can then loop over this array with a for loop, and grab each property from the passed in individual object which you can then set on your model and initialValues objects.
const desiredProps = ["a", "b", "d"]; // contains "initials", etc...
const model = {};
const initialValues = {};
function populateIndividualDetails(individual) {
for(const prop of desiredProps) {
model[prop] = individual[prop];
initialValues[prop] = individual[prop];
}
}
populateIndividualDetails({a: 1, b: 2, c: 3, d: 4}); // ignore "c" value
console.log(model);
console.log(initialValues);
EDIT
If you need model and initialValues to be populated initially, then it might be better to create one and deep-clone the other (as you mentioned you can have nested object properties) and then use recursion to handle the nested objects:
const model = {a: '', b: {c: ''}, e: ''};
const initialValues = JSON.parse(JSON.stringify(model)); // deep-clone model, both are their own object references.
function populateIndividualDetails(o1, o2, individual) {
Object.keys(o1).forEach(key => {
if(Object(o1[key]) === o1[key])
populateIndividualDetails(o1[key], o2[key], individual[key])
else
o1[key] = o2[key] = individual[key];
});
}
populateIndividualDetails(model, initialValues, {a: 1, b: {c: 2, d: 3}, e: 4, f: 5}); // ignore "f" value
console.log(model);
console.log(initialValues);
You can just destructurate and pick the properties of individual the following way:
function populateIndividualDetails({initials}: SelectedMemberIndividualData) {
this.model.initials = initials;
this.initialValue.initials = initials;
}
but if you have several properties you might want this but this will replace the entire model and initial value objects and they will point to the same object…
function populateIndividualDetails(individual: SelectedMemberIndividualData) {
const {a, b, c} = individual;
const copy = {a, b ,c}
this.model= copy;
this.initialValue= copy;
}
What you are probably looking for is to just add some properties to the existing model and initialValue objects
function populateIndividualDetails(individual: SelectedMemberIndividualData) {
const {a, b, c} = individual;
const propertiesToAdd = {a, b ,c}
this.model= {...this.model, ...propertiesToAdd};
this.initialValue= {...this.initialValue, ...propertiesToAdd};
}
Note:
anObject = {...anObject, ...anotherObject};// can be replaced with
Object.assign(anObject,anotherObject);
Using lodash you could also do this:
function getDefaultProperties(anObject: SelectedMemberIndividualData){
return _.pick(anObject, ['a','b','c'])
}
function populateIndividualDetails(individual: SelectedMemberIndividualData){
const defaultProperties = getDefaultProperties(individual);
[this.model, this.initialValue].forEach((value) => {
_.assign(value, defaultProperties);
})
}

Is there a way to traverse a possibly-self-containing object in JavaScript?

I want to descend an object in Javascript looking for a specific string. Unfortunately, this object is built in such a way that it'd be impossible to simply use the source and Ctrl-F for that string, and it's also built in such a way that recursive functions trying to descend it risk getting trapped inside of it forever.
Basically, this object contains itself. Not just once, but in very many areas. I cannot simply say "exclude these keys", as the object is obfuscated and therefore we'd be here all day listing keys, and once we were done we wouldn't have looked at all the data.
As well, I need to be able to descend __proto__ and prototype, as useful strings are hidden in there too. (But only for functions and objects.)
While I'd prefer something along the lines of findStuff(object, /string/ig), that may be hard, so any function that simply has areas clearly marked that the control flow falls to once it's found specific objects (function, string, etc.)
Thank you, and sorry for such a pain in the butt question.
Edit: In case it helps, I'm trying to traverse a compiled Construct2 runtime object. I'm not going to post the full thing here as it's not going to fit in any pastebin no matter how forgiving, and also I don't want to accidentally post resources I don't have the permission to provide. (Don't worry though, I'm not trying to pirate it myself, I'm simply trying to figure out some user-facing functionality)
You could use a WeakSet to keep track of the objects that were already traversed:
function traverseOnce(obj, cb) {
const visited = new WeakSet();
(function traverse(obj) {
for(const [key, value] of Object.entries(obj)) {
if(typeof value === "object" && value !== null) {
if(visited.has(value)) continue;
visited.add(value);
cb(value);
traverse(value);
}
}
})(obj);
}
Through the WeakSet you got O(1) lookup time, and are also sure that this will never leak.
Usable as:
const nested = { other: { a: 1 } };
nested.self = nested;
traverseOnce(nested, console.log);
// nested: { other, self }
// other: { a: 1 }
You could also use a Symbol to flag traversed objects, for that replace new WeakSet() with Symbol(), visited.has(value) with value[visited] and visuted.add(value) with value[visited] = true;
Any time you're traversing a potentially cyclical object, keeping a memo of already traversed objects and breaking if you've seen the current object before is a standard technique. You can use Set to do so.
Keep a list of objects you have recursed into, and then check each new object against that list.
const data = {
foo: {
bar: 1
},
one: 1,
jaz: {
hello: {
x: 1
}
}
};
data.bar = data.foo;
data.foo.foo = data.foo;
data.jaz.hello.foo = data;
function search_for_1() {
const seen = [];
search(data);
function search(object) {
Object.values(object).forEach(value => {
if (typeof value === "object") {
if (seen.includes(value)) {
console.log("Seen this already");
} else {
seen.push(value);
search(value);
}
} else {
if (value === 1) {
console.log("Found 1");
}
}
});
}
}
search_for_1();
Don't reinvent the wheel There are libraries for this kind of stuff.
We use object-scan for all our data processing. It's very powerful once you wrap your head around it. Here is how it would work for your questions
// const objectScan = require('object-scan');
const traverse = (data) => objectScan(['**'], {
filterFn: ({ key, value, parent }) => {
// do something here
},
breakFn: ({ isCircular }) => isCircular === true
})(data);
const circular = { name: 'Max', age: 5, sex: undefined, details: { color: 'black', breed: undefined } };
circular.sex = circular;
circular.details.breed = circular;
console.log(traverse(circular));
/* =>
[ [ 'details', 'breed' ],
[ 'details', 'color' ],
[ 'details' ],
[ 'sex' ],
[ 'age' ],
[ 'name' ] ]
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

Comparing Two JavaScript Objects and Pulling out Key and Value of Matching Property

If I know there's a property in common between two JavaScript objects called "req" and "updatedDoc" respectively, is there a way I can use a placeholder parameter to represent any key, so that I can find the right one that matches on the two objects? I tried this but it doesn't work:
for (const [key, val] of Object.entries(req)) {
if (key === updatedDoc[key]) {
console.log("key, val", key, val);
}
}
By the way, in my use case I know there will always be one matching property between the two objects. And to clarify, the two objects are called "req" and "updatedDoc". I don't know what they key will be, but I know the two objects will have one key in common.
To add a little more clarity, "req" is going to be something simple, like:
const req = {
"deleted" : true,
"apiKey" : "4d9d9291",
"token" : "ffdsfjsdfsdjfa"
}
... whereas updatedDoc will be a full document, like this:
const updatedDoc = {
_id: <ObjectId>,
firstName: "John",
lastName: "Smith",
age: 42
deleted: false
}
Both have a property called "deleted". Basically I'm matching a request passed in with the whole document it pertains to. I then want to take in the value from "req" and save it to "updatedDoc" for the correct key. But first I need to find the matching key, and pull out the value from "req". Is there a way I can do this?
You should be able to just modify your loop to change
if (key === updatedDoc[key]) to if (key in updatedDoc)
Everything inside this if statement will only be performed on keys that exist both in req and updatedDoc. The value stored for the key in req will be val, which was dereferenced from Object.entries
You can change updatedData to the new value like so updatedData[key] = val. You can also store the change in an array for later, if you like.
const updatedDoc = {
firstName: "John",
lastName: "Smith",
age: 42,
deleted: false
}
const req = {
"deleted": true,
"apiKey": "4d9d9291",
"token": "ffdsfjsdfsdjfa"
}
const changes = []
for (const [key, val] of Object.entries(req)) {
if (key in updatedDoc) {
// get the previous value
const oldVal = updatedDoc[key]
// update updatedDoc
updatedDoc[key] = val
// store the change or do whatever
changes.push({
[key]: {
new: val,
old: oldVal
}
})
}
}
console.log(updatedDoc)
console.log(changes)
Why not take a Set for the first object and filter the keys for the second object.
The result is an array with common keys.
var objectA = { a: 1, b: 2, c: 3 },
objectB = { b: 4, c: 5, d: 6 },
common = Object.keys(objectB).filter(Set.prototype.has, new Set(Object.keys(objectA))),
values = common.map(k => objectA[k]);
console.log(common);
console.log(values);
A bit shorter version.
var objectA = { a: 1, b: 2, c: 3 },
objectB = { b: 4, c: 5, d: 6 },
common = Object.keys(objectA).filter({}.hasOwnProperty.bind(objectB)),
values = common.map(k => objectA[k]);
console.log(common);
console.log(values);
Try this solution I think it will solve the problem
updatedDoc.forEach(function(i,v){
for (const [key, val] of Object.entries(req)) {
if (key === i && req[key]==updatedDoc[i]) {
console.log("key, val", key, val);
}
});

Is there a nice way in javascript to removing Falsy values from a javascript object (not an array)?

In JavaScript you have the nice .filter method to remove null or falsy values from arrays. So far I haven't been able to find a method to remove the same from JavaScript Objects.
Why would this be?
Currently you can create a function for arrays like :
function stripNulls(arr) {
return arr.filter(Boolean);
}
Is there a similar function that can be created for JS Objects, or is the way filter works not practical on JS Objects.
The answer to "can I do x to an object" (or an array for that matter) is usually "yes" and it frequently involves some form of reduce.
If you want to filter falsy values you could do something like this:
function filterFalsy(obj) {
return Object.keys(obj).reduce((acc, key) => {
if (obj[key]) {
acc[key] = obj[key]
}
return acc
}, {})
}
const testObj = {
a: 'test',
b: 321,
c: false
}
console.log(filterFalsy(testObj))
This returns a new object without falsy values and leaves the existing object alone.
WARNING: There are better answers provided here. Also, thanks to comments made below user's should be warned using delete may provide suboptimal performance.
Filtering invalid values is a little more complex in objects. At face value this will do what you want:
var arr = [ 'apple', 43, false ];
var trueArr = arr.filter(Boolean);
console.log(trueArr);
var obj = { 'title': 'apple', 'id': 43, 'isOrange': false, 'test': 'asd' };
Object.keys(obj)
.filter(key => !obj[key])
.forEach(key => delete obj[key]);
console.log(obj);
However, this will not iterate over child objects / functions. This logic also directly modifies the original object (which may or may not be desired).
That can easily changed by adding this logic to a function like so:
function removeFalseyProperties(obj) {
Object.keys(obj)
.filter(key => !obj[key])
.forEach(key => delete obj[key]);
return obj;
}
var testObj = { 'title': 'apple', 'id': 43, 'isOrange': false, 'test': 'asd' };
var trutheyObj = removeFalseyProperties(testObj);
console.log(trutheyObj);
falsy values are 0, undefined, null, false, etc.
myArray
.map(item => {
// ...
})
// Get rid of bad values
.filter(Boolean);
By passing Boolean we can remove all the falsy values.

Categories

Resources