This question already has answers here:
Replace nested field values in an object
(5 answers)
Closed 5 months ago.
Write a function that should take an array of objects and it should replace a particular value of the key?
for example: in below object, all the value of "bar" should be replaced to "foo-bar"
let obj = {
a: "foo",
b: "bar",
c: {
d: "foo",
e: "bar",
f: {
g: "test",
h: "bar",
}
}
}
Note: All the nested object properties also needs to be replaced
Recursively loop through all the keys.
const obj = {
a: 'foo',
b: 'bar',
c: {
d: 'foo',
e: 'bar',
f: {
g: 'test',
h: 'bar',
},
},
};
function replaceValuesInObj(o, targetValue, replaceValue) {
Object.keys(o).forEach((key) => { // forEach key in the object
if (o[key] === targetValue) o[key] = replaceValue; // is the value your condition? set it to 'foo-bar'
if (typeof o[key] === 'object' && o[key] !== null) { // if the key is an object (call the function recursively)
replaceValuesInObj(o[key], targetValue, replaceValue);
}
});
}
replaceValuesInObj(obj, 'bar', 'foo-bar');
console.log(obj);
Related
If I have an object with multiple child properties:
let obj = { a: "a", b: { c: "c", d: { e: "e" } } }
how can I get an object property's depth:
eg. "e" property depth is 4 (or 3) obj.b.d.e
A recursive method initialising depth as 1, using a separate function to check whether something is an actual object (given that almost everything in JS is of type "object").
const obj = { a: "a", b: { c: "c", d: { e: "e" } } }
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
function getDepth(obj, query, depth = 1) {
if (!isObject(obj)) return 'Not an object';
for (const [key, value] of Object.entries(obj)) {
if (key === query) return `Found ${query} at depth ${depth}`;
if (isObject(value)) return getDepth(value, query, ++depth);
}
return 'No property matching query';
}
console.log(getDepth(obj, 'b'));
console.log(getDepth(obj, 'c'));
console.log(getDepth(obj, 'e'));
I have a javascript plain object like this one: {a: {b: 1} }
and I want to convert it to a dot-notation string like this a.b = 1
use case:
sending the object to a plain-text environment such as cli or as a url parameter.
It's rather hard to tell whether this is what you want, but something like this would flatten a tree of objects into a list of dotted paths...
var data = {
a: {
b: 1,
c: {
d: 8
}
},
e: {
f: {
g: 9
},
h: 10,
i: [1, 2, 3]
}
};
function toDotList(obj) {
function walk(into, obj, prefix = []) {
Object.entries(obj).forEach(([key, val]) => {
if (typeof val === "object" && !Array.isArray(val)) walk(into, val, [...prefix, key]);
else into[[...prefix, key].join(".")] = val;
});
}
const out = {};
walk(out, obj);
return out;
}
console.log(toDotList(data));
This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 3 years ago.
I am trying to implement a clone function but I am not sure if I am doing it right while trying to clone '[object Function]'. You will see the result at the bottom. I am not sure if desired result should look like the original input data. Let me know what you think and if you have any ideas on how to implement it. Here is the code.
UPD: actually it works as it supposed to be working. I am going to leave it here so people can use it if they have the same question.
function deep(value) {
if (typeof value !== 'object' || value === null) {
return value;
}
if (Array.isArray(value)) {
return deepArray(value);
}
return deepObject(value);
}
function deepObject(source) {
const result = {};
Object.keys(source).forEach(key => {
const value = source[key];
result[key] = deep(value);
});
return result;
}
function deepArray(collection) {
return collection.map(value => {
return deep(value);
});
}
const id1 = Symbol('id');
const value = {
a: 2,
f: id1,
b: '2',
c: false,
g: [
{ a: { j: undefined }, func: () => {} },
{ a: 2, b: '2', c: false, g: [{ a: { j: undefined }, func: () => {} }] }
]
};
RESULT
{ a: 2,
f: Symbol(id),
b: '2',
c: false,
g:
[ { a: { j: undefined }, func: [Function: func] },
{ a: 2,
b: '2',
c: false,
g: [ { a: { j: undefined }, func: [Function: func] } ] } ] }
You cannot clone an arrow function, when you clone an object that has arrow functions as properties they will always be bound to the object they were created in, you cannot rebind them, that is the whole point of an arrow function, predictable behaviour of the this object. If you want to clone objects then make sure that any functions that refer to this are normal functions and not arrow functions.
Better use below single code for deepcopy -
function deepCopy(oldObj) {
var newObj = oldObj;
if (oldObj && typeof oldObj === "object") {
newObj = Object.prototype.toString.call(oldObj) === "[object Array]" ? [] : {};
for (var i in oldObj) {
newObj[i] = this.deepCopy(oldObj[i]);
}
}
return newObj;
}
I'd like to merge two similar but not identical objects and override null values in one of them, if such exist. For example I'd have these two objects:
const obj1 = {
a: 1,
b: '',
c: [],
d: null
}
const obj2 = {
a: 2,
b: null,
d: 1
}
And the effect of merge should be:
const objMerged = {
a: 2,
b: '',
c: [],
d: 1
}
In other words, the most important source of data in the merged object is obj2 but it lacks some properties from obj1, so they need to be copied and also some of the obj2 values are null so they should be taken from obj1 as well.
EDIT
I tried:
_.extend({}, obj1, obj2)
and
Object.assign({}, obj1, obj2)
You could also mix and match with ES6 destructuring and lodash _.omitBy:
const obj1 = { a: 1, b: '', c: [], d: null }
const obj2 = { a: 2, b: null, d: 1 }
const result = {..._.omitBy(obj1, _.isNull), ..._.omitBy(obj2, _.isNull)}
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
You could also do it with ES6 only like this:
const obj1 = { a: 1, b: '', c: [], d: null }
const obj2 = { a: 2, b: null, d: 1 }
let omitNull = obj => {
Object.keys(obj).filter(k => obj[k] === null).forEach(k => delete(obj[k]))
return obj
}
const result = { ...omitNull(obj1), ...omitNull(obj2) }
console.log(result)
To add to this list of good answers, here's a recursive solution that will work with nested structures.
This example will merge the common properties of the dst object to the src object in all levels of nesting, leaving any properties that are not common intact.
const merge = (dst, src) => {
Object.keys(src).forEach((key) => {
if (!dst[key]) {
dst[key] = src[key];
} else if (typeof src[key] === 'object' && src[key] !== null && typeof dst[key] === 'object' && dst[key] !== null) {
merge(dst[key], src[key]);
}
});
},
/* Usage: */
src = {
prop1: '1',
prop2: {
val: 2,
}
},
dst = {
prop1: null,
prop2: {
val: null,
},
prop3: null,
};
merge(dst, src);
console.log(dst);
You can use _.mergeWith(), and in the merge callback only take the 2nd value if it's not null:
const obj1 = { a: 1, b: '', c: [], d: null }
const obj2 = { a: 2, b: null, d: 1 }
const result = _.mergeWith({}, obj1, obj2, (o, s) => _.isNull(s) ? o : s)
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
Here is a pure JS based solution:
Iterate through the first object to replace values from second object, then add the additional values from the second object.
const obj1 = {
a: 1,
b: '',
c: [],
d: null
}
const obj2 = {
a: 2,
b: null,
d: 1
}
function mergeObjs(obj1, obj2){
const merged = {}
keys1 = Object.keys(obj1);
keys1.forEach(k1 => {
merged[k1] = obj2[k1] || obj1[k1]; // replace values from 2nd object, if any
})
Object.keys(obj2).forEach(k2 => {
if (!keys1.includes(k2)) merged[k2] = obj[k2]; // add additional properties from second object, if any
})
return merged
}
console.log(mergeObjs(obj1, obj2))
Using Lodash by create() and omitBy()
const obj1 = {"a":1,"b":"","c":[],"d":null}
const obj2 = {"a":2,"b":null,"d":1}
const objMerged = _.create(
_.omitBy(obj1, _.isNull),
_.omitBy(obj2, _.isNull)
)
console.log(objMerged)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
If you're interested in only the first level of the two objects you could do something like this:
const obj1 = {
a: 1,
b: '',
c: [],
d: null
}
const obj2 = {
a: 2,
b: null,
d: 1
}
const merged = Object.keys(obj1).concat(Object.keys(obj2)) // create an array that contains the keys of the two objects.
.filter((k, i, arr) => arr.indexOf(k) === i) // remove duplicate keys
.reduce((a, c) => {
a[c] = obj1[c] !== null ? obj1[c] : obj2[c];
return a;
}, {});
console.log(merged);
This example only check for null values, you should probably extend it to check for others like undefined, empty strings, etc.
You did it the good way using Object.assign, just remove what you don't want right before
Object.keys(obj1).forEach( k => {
if ( obj1[k] //write the condition you want
delete obj1[k]
});
var objMerged = {};
for (var kobj1 in obj1) {
for (var kobj2 in obj2) {
if (obj1[kobj1] == null && obj2[kobj1] != null)
objMerged[kobj1] = obj2[kobj1];
else if (obj2[kobj2] == null && obj1[kobj2] != null)
objMerged[kobj2] = obj1[kobj2];
}
}
//Print objMerged to display
Suppose I have the following array of objects:
var list = [
{ a: 1,
b: { c: 'x', k: []}
},
{ a: 1,
b: {c: 'x', d: 8}
}
];
I want them to be merged into one "generic" object, for this example, it would be:
{a: 1, b: {c: 'x', d:'8', k[]}}
As you can see, all nested objects are merged too. But I can't gain it. If I use Object.assign it creates new nested objects if they are different, that is duplicates them:
var res = Object.assign({}, ...list);
// res: {
a: 1,
b: {c: 'x', k: []},
b: {c: 'x', d: 8}
}
You could try the following using the reduce method:
var list = [{
a: 1,
b: {
a: 4,
k: 3
}
}, {
a: 1,
s: 11,
b: {
ab: 4,
d: 8
}
}]
var result = list.reduce(function(acc, item) {
var obj = { ...item
}
Object.keys(obj).forEach(function(item) {
if (acc[item]) { //if a property with the the key, 'item' already exists, then append to that
Object.assign(acc[item], obj[item]);
} else { // else add the key-value pair to the accumulator object.
acc[item] = obj[item];
}
})
return acc;
}, {})
console.log(result);
Deep merging is not simple to do yourself, That blog uses deep merge.
If you don't have webpack or nodejs you can use deepmerge like so:
// see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25
var canUseSymbol = typeof Symbol === 'function' && Symbol.for
var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7
function isReactElement(value) {
return value.$$typeof === REACT_ELEMENT_TYPE
}
function isNonNullObject(value) {
return !!value && typeof value === 'object'
}
function isSpecial(value) {
var stringValue = Object.prototype.toString.call(value)
return stringValue === '[object RegExp]'
|| stringValue === '[object Date]'
|| isReactElement(value)
}
function defaultIsMergeableObject(value) {
return isNonNullObject(value)
&& !isSpecial(value)
}
function emptyTarget(val) {
return Array.isArray(val) ? [] : {}
}
function cloneUnlessOtherwiseSpecified(value, options) {
return (options.clone !== false && options.isMergeableObject(value))
? deepmerge(emptyTarget(value), value, options)
: value
}
function defaultArrayMerge(target, source, options) {
return target.concat(source).map(function(element) {
return cloneUnlessOtherwiseSpecified(element, options)
})
}
function mergeObject(target, source, options) {
var destination = {}
if (options.isMergeableObject(target)) {
Object.keys(target).forEach(function(key) {
destination[key] = cloneUnlessOtherwiseSpecified(target[key], options)
})
}
Object.keys(source).forEach(function(key) {
if (!options.isMergeableObject(source[key]) || !target[key]) {
destination[key] = cloneUnlessOtherwiseSpecified(source[key], options)
} else {
destination[key] = deepmerge(target[key], source[key], options)
}
})
return destination
}
function deepmerge(target, source, options) {
options = options || {}
options.arrayMerge = options.arrayMerge || defaultArrayMerge
options.isMergeableObject = options.isMergeableObject || defaultIsMergeableObject
var sourceIsArray = Array.isArray(source)
var targetIsArray = Array.isArray(target)
var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray
if (!sourceAndTargetTypesMatch) {
return cloneUnlessOtherwiseSpecified(source, options)
} else if (sourceIsArray) {
return options.arrayMerge(target, source, options)
} else {
return mergeObject(target, source, options)
}
}
deepmerge.all = function deepmergeAll(array, options) {
if (!Array.isArray(array)) {
throw new Error('first argument should be an array')
}
return array.reduce(function(prev, next) {
return deepmerge(prev, next, options)
}, {})
}
var list = [{
a: 1,
b: {
c: 'x',
//merging 1,2 and 1,3 results in [1,2,1,3] you can change that in defaultArrayMerge
k: [1,2]
}
},
{
a: 1,
b: {
c: 'x',
k: [1,3],
d: 8
}
}];
console.log(
deepmerge.all(list)
)
You can use the reduce method. Remove the first element from the original list , that object will be the base method.
var list = [{
a: 1,
b: {
c: 'x',
k: []
}
},
{
a: 1,
b: {
c: 'x',
d: 8
}
}
];
// Remove the first element from the array. The first element will be
// the base object
// slice will return a new array without the first object
// apply reduce on this list
let _temp = list.slice(1);
let x = _temp.reduce(function(acc,curr,currIndex){
for(let keys in curr){
// checking if the base object have the same key as of current object
if(acc.hasOwnProperty(keys)){
// if base object and current object has the key then
// check if the type is an object
if(typeof curr[keys] ==='object'){
// now get the key from both the object
// & check which one is missong. Add that key and value to the
// base object
let keysFromACC = Object.keys(acc[keys]);
let keysFromCURR = Object.keys(curr[keys]);
keysFromCURR.forEach(function(item){
if(keysFromACC.indexOf(item) ===-1){
acc[keys][item] = curr[keys][item]
}
})
}
}
else{
// if the base object does not have key which current object
// has then add the key to base object
acc[keys]= curr[keys]
}
}
return acc;
},list[0]);
console.log(x)