How to merge values from multiple objects into an object of arrays? - javascript

I have a two JSON something like below:
var obj1 = {
" name ":"rencho",
" age ":23,
" occupation ":"SE"
}
var obj2 = {
" name ":"manu",
" age ":23,
" country ":"india"
}
I want the expected output:
var result = {
"name":["rencho", "manu"],
"age":[23, 23],
"country":["-", "india"],
"occupation": ["SE", "-"],
}
However, I tried using below the code snippet:
let arrGlobal = []
arrGlobal.push(obj1);
arrGlobal.push(obj2);
let mergedResult = arrGlobal.reduce(function(r, e) {
return Object.keys(e).forEach(function(k) {
if(!r[k]) r[k] = [].concat(e[k])
else r[k] = r[k].concat(e[k])
}), r
}, {})
console.log(mergedResult);
But that one doesn't print - in json object. I would appreciate any kind of help from your side.
HELP WOULD BE APPRECIATED!!!

First get a list of all keys (needed in advance to check whether you need to add - while iterating), then use reduce to iterate over each object and add its values to the accumulator:
var obj1 = {
" name ":"rencho",
" age ":23,
" occupation ":"SE"
}
var obj2 = {
" name ":"manu",
" age ":23,
" country ":"india"
}
const arr = [obj1, obj2];
const allKeys = arr.reduce((keys, obj) => {
Object.keys(obj).forEach(key => keys.add(key))
return keys;
}, new Set());
const merged = arr.reduce((merged, obj) => {
allKeys.forEach((key) => {
if (!merged[key]) merged[key] = [];
merged[key].push(obj[key] || '-');
});
return merged;
}, {});
console.log(merged);

A slightly different approach by using a single loop for the outer array of objects and which generates all needed keys on the fly.
var obj1 = { name: "rencho", age: 23, occupation: "SE" },
obj2 = { name: "manu", age: 23, country: "india" },
hash = new Set,
result = {};
[obj1, obj2].forEach((o, length) => {
Object.keys(o).forEach(k => hash.add(k));
hash.forEach(k => {
result[k] = result[k] || Array.from({ length }).fill('-');
result[k].push(k in o ? o[k] : '-');
});
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

quick and dirty way:
function merge(a,b) {
var c = b
for (key in a){
c[key] = [c[key], a[key]]
}
console.log(c) //prints merged object
}
merge(obj1, obj2)

Related

Generated a Nested Object from TLV

I am trying to parse a TLV string and need to generate a nested object from that string.
const text = 'AA06ClaireCC04JackBB03TomEE05James'
The output needs to look like this:
"Acrobatic Artist": {
"AA": {
"Claire": {
"Curious Camper": {
"CC": {
"Jack": {
"Baddest Banana": {
"BB": {
"Tom": {
"Energetic Elephant": {
"EE": {
"James" : "LASTRECORD"
}
}
}
}
}
}
}
}
}
}
}
Here is what I currently have:
const map = {
AA: 'Acrobatic Artist',
BB: 'Baddest Banana',
CC: 'Curious Camper',
DD: 'Desperate Driver',
EE: 'Energetic Elephant'
}
function createJson(str) {
let json = {}
let remainingText = str
while(remainingText.length > 0) {
const tag = remainingText.substring(0, 2)
const len = remainingText.substring(2, 4)
const val = remainingText.substring(4, len)
const offset = tag.length + len.length + parseInt(len, 16)
remainingText = remainingText.substring(offset)
console.log('new text: ' + remainingText)
json[map[tag]] = {}
json[map[tag]][tag] = {}
json[map[tag]][tag][val] = {}
}
return json
}
But this just creates an object that looks like this:
{
Acrobatic Artist: {
AA: {
Claire: {}
}
},
Baddest Banana: {
BB: {
Tom: {}
}
},
Curious Camper: {
CC: {
Jack: {}
}
},
Energetic Elephant: {
EE: {
James: {}
}
}
}
Here is my fiddle:
https://jsfiddle.net/kzaiwo/y9m2h60t/8/
Note:
Please disregard the LASTRECORD part. I just added that to complete the key-value pair (for the last pair) in the above example. Thank you!
Thanks!
If you keep a reference to a prev value, which starts off as the original json object, you can then continuously update it and its children. When you're updating your object within the while loop you can update prev, and set it to the last child object that you create so that on the next iteration of your loop that particular child object will be updated to contain the new key-value pairs.
const map = {
AA: 'Acrobatic Artist',
BB: 'Baddest Banana',
CC: 'Curious Camper',
DD: 'Desperate Driver',
EE: 'Energetic Elephant'
};
const text = 'AA06ClaireCC04JackBB03TomEE05James';
function createJson(str) {
let json = {};
let prev = json;
let remainingText = str;
while (remainingText.length > 0) {
const tag = remainingText.substring(0, 2);
const len = remainingText.substring(2, 4);
const val = remainingText.substring(4, 4 + parseInt(len, 16));
const offset = tag.length + len.length + parseInt(len, 16);
remainingText = remainingText.substring(offset);
prev[map[tag]] = {};
prev[map[tag]][tag] = {};
prev = prev[map[tag]][tag][val] = {};
}
return json;
}
console.log(createJson(text));
Given the regular structure of your string (2-character code + 2-character number + characters), you can use a simple regex to split out the various parts.
From there you can (flat) map each section into an array of keys.
Finally, you can reduce-right the array to produce the result you want.
const map = {AA:"Acrobatic Artist",BB:"Baddest Banana",CC:"Curious Camper",DD:"Desperate Driver",EE:"Energetic Elephant"};
const text = "AA06ClaireCC04JackBB03TomEE05James";
// Parses a code, length and value from the start of the provided string
const parseSection = (str) => {
const [, code, valueLength] = str.match(/^(\w{2})([a-fA-F0-9]{2})/);
const length = parseInt(valueLength, 16) + 4;
return {
code,
length,
type: map[code],
value: str.slice(4, length),
};
};
// Iterates through the string, extracting sections until finished
const parseTlv = (str) => {
const sections = [];
while (str.length) {
const section = parseSection(str);
sections.push(section);
str = str.slice(section.length);
}
return sections;
};
// Map each section to a flat array of keys then reduce-right to form
// a tree structure
const lastRecord = {};
const result = parseTlv(text)
.flatMap(({ type, code, value }) => [type, code, value])
.reduceRight(
(obj, key) => ({
[key]: obj,
}),
lastRecord
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; }
Here is a two part solution:
for() loop: create an array of items based on <tag><len><val> patterns
.reduce(): build the nested object from the items array
const input = 'AA06ClaireCC04JackBB03TomEE05James';
const tagMap = {
AA: 'Acrobatic Artist',
BB: 'Baddest Banana',
CC: 'Curious Camper',
DD: 'Desperate Driver',
EE: 'Energetic Elephant'
};
let items = [];
for(let i = 0; i < input.length; ) {
let tag = input.substring(i, i + 2);
let len = parseInt(input.substring(i + 2, i + 4), 16);
let val = input.substring(i + 4, i + 4 + len);
items.push([tag, val]);
i += 4 + len;
}
let result = {};
items.reduce((obj, arr) => {
const tag = arr[0];
const val = arr[1];
const name = tagMap[tag] || 'unknown';
//console.log(name, tag, val);
[name, tag, val].forEach(key => {
obj[key] = {};
obj = obj[key];
});
return obj;
}, result);
console.log(result);
Output:
{
"Acrobatic Artist": {
"AA": {
"Claire": {
"Curious Camper": {
"CC": {
"Jack": {
"Baddest Banana": {
"BB": {
"Tom": {
"Energetic Elephant": {
"EE": {
"James": {}
}
}
}
}
}
}
}
}
}
}
}
}
Note: the resulting object has an empty {} as the innermost value; you could replace that with "LASTRECORD" if needed

Javascript: Dot Notation Strings to Nested Object References

We're trying to set nested object values based on dot notation strings.
Example input:
{
"bowtime": [
"30",
" 1",
" 3",
" 20"
],
"bowstate.levi.leviFlo.totalFloQuot": ".95",
"bowstate.crem.cremQuot": "79"
}
Desired output:
{
"bowstate": {
"levi": {
"leviFlo": {
"totalFloQuot": 0.95
}
},
"crem": {
"cremQuot": 79
}
},
"bowtime": [
"30",
" 1",
" 3",
" 20"
],
}
So far the code works fine, but it seems overly complex, and only allows for 4 layers of nesting. How can we simplify this code, and get it working for references with more than 4 layers of nesting:
const dayspace = {};
var keyArr = Object.keys(input);
for (key in keyArr) {
if ( keyArr[key].indexOf('.') > -1 ) {
var setArr = keyArr[key].split('.');
dayspace[setArr[0]] = dayspace[setArr[0]] || {}
for (var s = 0; s < setArr.length; s++) {
if (s == 1) {
if (setArr.length > s + 1) dayspace[setArr[0]][setArr[s]] = {}
else dayspace[setArr[0]][setArr[s]] = req.body[keyArr[key]]
}
if (s == 2) {
if (setArr.length > s + 1) dayspace[setArr[0]][setArr[1]][setArr[s]] = {}
else dayspace[setArr[0]][setArr[1]][setArr[s]] = req.body[keyArr[key]]
}
if (s == 3) {
if (setArr.length > s + 1) dayspace[setArr[0]][setArr[1]][setArr[2]][setArr[s]] = {}
else dayspace[setArr[0]][setArr[1]][setArr[2]][setArr[s]] = req.body[keyArr[key]]
}
if (s == 4) dayspace[setArr[0]][setArr[1]][setArr[2]][setArr[3]][setArr[s]] = req.body[keyArr[key]]
}
}
else {
dayspace[keyArr[key]] = req.body[keyArr[key]]
}
}
I'd split the key by . and use reduce to create all but the last nested value, if needed, and then assign the value to the last object created or found in the reduce callback:
const input = {
"bowtime": [
"30",
" 1",
" 3",
" 20"
],
"bowstate.levi.leviFlo.totalFloQuot": ".95",
"bowstate.crem.cremQuot": "79"
};
const output = Object.entries(input).reduce((outerObj, [key, val]) => {
if (!key.includes('.')) {
outerObj[key] = val;
return outerObj;
}
const keys = key.split('.');
const lastKey = keys.pop();
const lastObj = keys.reduce((a, key) => {
// Create an object at this key if it doesn't exist yet:
if (!a[key]) {
a[key] = {};
}
return a[key];
}, outerObj);
// We now have a reference to the last object created (or the one that already existed
// so, just assign the value:
lastObj[lastKey] = val;
return outerObj;
}, {});
console.log(output);
I have done similar things in my project. I have achieved it with a popular package called Flat. Link: https://github.com/hughsk/flat
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
This package can make your nested structure flat and flatten structure nested as well. There are other useful methods there also. So it will be more flexible.
I think you should use it which will lead to less bugs in your project.
You could use Object.entires to get an array of key-value pairs within your object and then .reduce() your object keys by using .split(".") to get the single object properties into an array which you can then use to build your new object:
const obj = {
"bowtime": [
"30",
" 1",
" 3",
" 20"
],
"bowstate.levi.leviFlo.totalFloQuot": ".95",
"bowstate.crem.cremQuot": "79"
};
const res = Object.entries(obj).reduce((acc, [k, v]) => {
const keys = k.split('.');
let cur = acc;
keys.length > 1 && keys.forEach(ka => {
cur[ka] = cur[ka] || {};
cur = cur[ka];
});
cur[keys.pop()] = v;
return acc;
}, {});
console.log(res);
You could use a shorter approach by using a function for the splitted path to the value and generate new objects for it.
function setValue(object, path, value) {
var last = path.pop();
path.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
}
var object = { bowtime: ["30", " 1", " 3", " 20" ], "bowstate.levi.leviFlo.totalFloQuot": ".95", "bowstate.crem.cremQuot": "79" };
Object.entries(object).forEach(([key, value]) => {
if (!key.includes('.')) return;
setValue(object, key.split('.'), value);
delete object[key];
});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Add values of elements in array using javascript with out using const

I had an array with
0:{Name: "Test1",Cost:100,Revenue:200}
1:{Name: "Test2",Cost:100,Revenue:100}
2:{Name: "Test3",Cost:100,Revenue:300}
3:{Name: "Test2",Cost:200,Revenue:100}
4:{Name: "Test1",Cost:100,Revenue:300}
5:{Name: "Test4",Cost:100,Revenue:300}
I am expecting result with out duplicates based on names and other values (cost and revenue) as added
0:{Name: "Test1",Cost:200,Revenue:500}
1:{Name: "Test2",Cost:300,Revenue:200}
2:{Name: "Test3",Cost:100,Revenue:300}
3:{Name: "Test4",Cost:100,Revenue:300}
I am trying with below code
var removeDuplicates = function(originalArray, prop) {
var newArray = [];
var lookupObject = {};
for(var i in originalArray) {
lookupObject[originalArray[i][prop]] = originalArray[i];
}
for(i in lookupObject) {
newArray.push(lookupObject[i]);
}
return newArray;
}
console.log('Name array ',removeDuplicates(tempRoleMap, 'Name'));
Can you help me out
You can use reduce to iterate over the array while assigning to an accumulator object indexed by Name. On each array item, either assign it as the new object at that key in the accumulator if it doesn't exist yet, or iterate over the item's entries (other than Name) and add to the appropriate property in the accumulator.
At the end, you'll have an object indexed by Names, so to get the array out of the object, call Object.values on it:
const arr=[{Name:"Test1",Cost:100,Revenue:200},{Name:"Test2",Cost:100,Revenue:100},{Name:"Test3",Cost:100,Revenue:300},{Name:"Test2",Cost:200,Revenue:100},{Name:"Test1",Cost:100,Revenue:300},{Name:"Test4",Cost:100,Revenue:300}]
const result = Object.values(arr.reduce((a, { Name, ...rest }) => {
if (!a[Name]) a[Name] = { Name, ...rest };
else {
Object.entries(rest).forEach(([key, val]) => {
a[Name][key] += val;
});
}
return a;
}, {}));
console.log(result);
It's best to use ES6+ when writing code, and then you can transpile it down to ES5 automatically with Babel and polyfills. But, if you had to write in ES5, then:
var arr=[{Name:"Test1",Cost:100,Revenue:200},{Name:"Test2",Cost:100,Revenue:100},{Name:"Test3",Cost:100,Revenue:300},{Name:"Test2",Cost:200,Revenue:100},{Name:"Test1",Cost:100,Revenue:300},{Name:"Test4",Cost:100,Revenue:300}]
var obj = arr.reduce(function(a, item) {
var Name = item.Name;
if (!a[Name]) a[Name] = item;
else {
Object.keys(item).forEach(function(key) {
if (key === 'Name') return;
a[Name][key] += item[key];
});
}
return a;
}, {});
var output = [];
Object.keys(obj).forEach(function(key) {
output.push(obj[key]);
});
console.log(output);
As you can see, it's a whole lot wordier and inelegant - better to write in the latest and greatest version of the language, and transpile down to your target environment automatically later.
You can get the desired output using .reduce() and Object.values():
let data = [
{Name: "Test1", Cost:100, Revenue:200},
{Name: "Test2", Cost:100, Revenue:100},
{Name: "Test3", Cost:100, Revenue:300},
{Name: "Test2", Cost:200, Revenue:100},
{Name: "Test1", Cost:100, Revenue:300},
{Name: "Test4", Cost:100, Revenue:300}
];
let result = Object.values(
data.reduce((r, c) => {
r[c.Name] = r[c.Name] || {};
r[c.Name].Cost = (r[c.Name].Cost || 0) + c.Cost;
r[c.Name].Revenue = (r[c.Name].Revenue || 0) + c.Revenue;
return r;
}, {})
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You will find a solution in the snippet below:
var arr = [{Name: "Test1",Cost:100,Revenue:200},
{Name: "Test2",Cost:100,Revenue:100},
{Name: "Test3",Cost:100,Revenue:300},
{Name: "Test2",Cost:200,Revenue:100},
{Name: "Test1",Cost:100,Revenue:300},
{Name: "Test4",Cost:100,Revenue:300}];
function removeDuplicates(arr, prop){
arr.forEach((a, i) => arr.slice(i+1).filter(e => e[prop] == a[prop]).forEach( el => {
if (arr.indexOf(el) > -1)
arr.splice(arr.indexOf(el), 1);
}));
return arr;
}
console.log(removeDuplicates(arr, 'Name'));

Transform an array to an object with nested properties according to the array

I have several arrays as the following:
[ 'businessOpenAccount', 'accountSettings1.page.js' ]
[ 'businessOpenAccount', 'accountSettings2.page.js' ]
[ 'mainTest', 'test', 'test1.page.js' ]
[ 'mainTest', 'test', 'test2.page.js' ]
My expected result is to have an object in this way:
{
businessOpenAccount: {
'accountSettings1.page.js': {},
'accountSettings2.page.js': {}
},
mainTest: {
test: {
'test1.page.js': {},
'test2.page.js': {}
}
}
}
So actually I want to parse the arrays and build a nested object to return from them, but being sure to check that if a property already exists (because defined from a previous array) I won't override it, but just add the new nested property in it, respecting the correct order of nesting.
I tried few approaches using reduce, reduceRight and simple forEach/for loops but I still cannot really achieve the solution I would like.
Any tips please?
This is the best way I have so far, but I override the properties cycling over multiple arrays (the example with a single array):
const relevantFilePath = ['businessOpenAccount', 'accountSettings.page.js'];
let obj = {};
relevantFilePath.forEach((el, ind) => {
if (ind === 0) {
obj[el] = {};
previousEl = obj[el];
} else {
previousEl[el] = {};
previousEl = previousEl[el];
}
});
console.log(obj);
You could iterate the given data and reduce the object and take the last item with splitted value.
var data = [['businessOpenAccount', 'accountSettings1.page.js'], ['businessOpenAccount', 'accountSettings2.page.js'], ['mainTest', 'test', 'test1.page.js'], ['mainTest', 'test', 'test2.page.js']],
object = {};
data.forEach(function (a) {
var last = a.pop().split('.')[0];
a.reduce(function (o, k) {
return o[k] = o[k] || {};
}, object)[last] = '';
});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
ES6 with without mutating original data
var data = [['businessOpenAccount', 'accountSettings1.page.js'], ['businessOpenAccount', 'accountSettings2.page.js'], ['mainTest', 'test', 'test1.page.js'], ['mainTest', 'test', 'test2.page.js']],
object = {};
data.forEach(function (a) {
var temp = a.slice(),
last = temp.pop().split('.')[0];
temp.reduce((o, k) => o[k] = o[k] || {}, object)[last] = '';
});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A recursive solution which would work for anykind of sub array with unlimited depth.
const a = [
['businessOpenAccount', 'accountSettings1.page.js'],
['businessOpenAccount', 'accountSettings2.page.js'],
[ 'businessOpenAccount', 'test1',
[
['test2', 'test2.settings.page.js',
[
['test2', 'test2.settings.page.js'],
['test3', 'test3.settings.page.js'],
],
'test4',
],
['test3', 'test3.settings.page.js'],
]
],
['mainTest', 'test', 'test1.page.js'],
['mainTest', 'test', 'test2.page.js'],
];
const result = {};
const traverse = (result, arr) => {
let firstEl = arr.shift();
if (firstEl instanceof Array) {
if (arr.length >= 1) {
traverseTop(result, firstEl);
return traverse(result, arr);
}
return traverseTop(result, firstEl);
}
firstEl = firstEl.split('.')[0];
result[firstEl] = arr.length >= 1 ? (result[firstEl] || {}) : '';
if (arr.length >= 1) {
return traverse(result[firstEl], arr);
}
return;
};
const traverseTop = (result, arr) => {
arr.forEach((subArr) => {
const firstEl = subArr.shift().split('.')[0];
result[firstEl] = result[firstEl] || {};
traverse(result[firstEl], subArr)
});
return result;
};
console.log(traverseTop(result, a));

Removing object properties with Lodash

I have to remove unwanted object properties that do not match my model. How can I achieve it with Lodash?
My model is:
var model = {
fname: null,
lname: null
}
My controller output before sending to the server will be:
var credentials = {
fname: "xyz",
lname: "abc",
age: 23
}
I am aware I can use
delete credentials.age
but what if I have lots of unwanted properties? Can I achieve it with Lodash?
You can approach it from either an "allow list" or a "block list" way:
// Block list
// Remove the values you don't want
var result = _.omit(credentials, ['age']);
// Allow list
// Only allow certain values
var result = _.pick(credentials, ['fname', 'lname']);
If it's reusable business logic, you can partial it out as well:
// Partial out a "block list" version
var clean = _.partial(_.omit, _, ['age']);
// and later
var result = clean(credentials);
Note that Lodash 5 will drop support for omit
A similar approach can be achieved without Lodash:
const transform = (obj, predicate) => {
return Object.keys(obj).reduce((memo, key) => {
if(predicate(obj[key], key)) {
memo[key] = obj[key]
}
return memo
}, {})
}
const omit = (obj, items) => transform(obj, (value, key) => !items.includes(key))
const pick = (obj, items) => transform(obj, (value, key) => items.includes(key))
// Partials
// Lazy clean
const cleanL = (obj) => omit(obj, ['age'])
// Guarded clean
const cleanG = (obj) => pick(obj, ['fname', 'lname'])
// "App"
const credentials = {
fname:"xyz",
lname:"abc",
age:23
}
const omitted = omit(credentials, ['age'])
const picked = pick(credentials, ['age'])
const cleanedL = cleanL(credentials)
const cleanedG = cleanG(credentials)
Get a list of properties from model using _.keys(), and use _.pick() to extract the properties from credentials to a new object:
var model = {
fname:null,
lname:null
};
var credentials = {
fname:"xyz",
lname:"abc",
age:23
};
var result = _.pick(credentials, _.keys(model));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.min.js"></script>
If you don't want to use Lodash, you can use Object.keys(), and Array.prototype.reduce():
var model = {
fname:null,
lname:null
};
var credentials = {
fname:"xyz",
lname:"abc",
age:23
};
var result = Object.keys(model).reduce(function(obj, key) {
obj[key] = credentials[key];
return obj;
}, {});
console.log(result);
You can easily do this using _.pick:
var model = {
fname: null,
lname: null
};
var credentials = {
fname: 'abc',
lname: 'xyz',
age: 2
};
var result = _.pick(credentials, _.keys(model));
console.log('result =', result);
<script src="https://cdn.jsdelivr.net/lodash/4.16.4/lodash.min.js"></script>
But you can simply use pure JavaScript (specially if you use ECMAScript 6), like this:
const model = {
fname: null,
lname: null
};
const credentials = {
fname: 'abc',
lname: 'xyz',
age: 2
};
const newModel = {};
Object.keys(model).forEach(key => newModel[key] = credentials[key]);
console.log('newModel =', newModel);
Lodash unset is suitable for removing a few unwanted keys.
const myObj = {
keyOne: "hello",
keyTwo: "world"
}
unset(myObj, "keyTwo");
console.log(myObj); /// myObj = { keyOne: "hello" }
Here I have used omit() for the respective 'key' which you want to remove... by using the Lodash library:
var credentials = [{
fname: "xyz",
lname: "abc",
age: 23
}]
let result = _.map(credentials, object => {
return _.omit(object, ['fname', 'lname'])
})
console.log('result', result)
You can use _.omit() for emitting the key from a JSON array if you have fewer objects:
_.forEach(data, (d) => {
_.omit(d, ['keyToEmit1', 'keyToEmit2'])
});
If you have more objects, you can use the reverse of it which is _.pick():
_.forEach(data, (d) => {
_.pick(d, ['keyToPick1', 'keyToPick2'])
});
To select (or remove) object properties that satisfy a given condition deeply, you can use something like this:
function pickByDeep(object, condition, arraysToo=false) {
return _.transform(object, (acc, val, key) => {
if (_.isPlainObject(val) || arraysToo && _.isArray(val)) {
acc[key] = pickByDeep(val, condition, arraysToo);
} else if (condition(val, key, object)) {
acc[key] = val;
}
});
}
https://codepen.io/aercolino/pen/MWgjyjm
This is my solution to deep remove empty properties with Lodash:
const compactDeep = obj => {
const emptyFields = [];
function calculateEmpty(prefix, source) {
_.each(source, (val, key) => {
if (_.isObject(val) && !_.isEmpty(val)) {
calculateEmpty(`${prefix}${key}.`, val);
} else if ((!_.isBoolean(val) && !_.isNumber(val) && !val) || (_.isObject(val) && _.isEmpty(val))) {
emptyFields.push(`${prefix}${key}`);
}
});
}
calculateEmpty('', obj);
return _.omit(obj, emptyFields);
};
For array of objects
model = _.filter(model, a => {
if (!a.age) { return a }
})
Recursively removing paths.
I just needed something similar, not removing just keys, but keys by with paths recursively.
Thought I'd share.
Simple readable example, no dependencies
/**
* Removes path from an object recursively.
* A full path to the key is not required.
* The original object is not modified.
*
* Example:
* const original = { a: { b: { c: 'value' } }, c: 'value' }
*
* omitPathRecursively(original, 'a') // outputs: { c: 'value' }
* omitPathRecursively(original, 'c') // outputs: { a: { b: {} } }
* omitPathRecursively(original, 'b.c') // { a: { b: {} }, c: 'value' }
*/
export const omitPathRecursively = (original, path, depth = 1) => {
const segments = path.split('.')
const final = depth === segments.length
return JSON.parse(
JSON.stringify(original, (key, value) => {
const match = key === segments[depth - 1]
if (!match) return value
if (!final) return omitPathRecursively(value, path, depth + 1)
return undefined
})
)
}
Working example: https://jsfiddle.net/webbertakken/60thvguc/1/
While looking for a solution that would work for both arrays and objects, I didn't find one and so I created it.
/**
* Recursively ignore keys from array or object
*/
const ignoreKeysRecursively = (obj, keys = []) => {
const keyIsToIgnore = (key) => {
return keys.map((a) => a.toLowerCase()).includes(key)
}
const serializeObject = (item) => {
return Object.fromEntries(
Object.entries(item)
.filter(([key, value]) => key && value)
.reduce((prev, curr, currIndex) => {
if (!keyIsToIgnore(curr[0]))
prev[currIndex] =
[
curr[0],
// serialize array
Array.isArray(curr[1])
? // eslint-disable-next-line
serializeArray(curr[1])
: // serialize object
!Array.isArray(curr[1]) && typeof curr[1] === 'object'
? serializeObject(curr[1])
: curr[1],
] || []
return prev
}, []),
)
}
const serializeArray = (item) => {
const serialized = []
for (const entry of item) {
if (typeof entry === 'string') serialized.push(entry)
if (typeof entry === 'object' && !Array.isArray(entry)) serialized.push(serializeObject(entry))
if (Array.isArray(entry)) serialized.push(serializeArray(entry))
}
return serialized
}
if (Array.isArray(obj)) return serializeArray(obj)
return serializeObject(obj)
}
// usage
const refObject = [{name: "Jessica", password: "ygd6g46"}]
// ignore password
const obj = ignoreKeysRecursively(refObject, ["password"])
// expects returned array to only have name attribute
console.log(obj)
let asdf = [{"asd": 12, "asdf": 123}, {"asd": 121, "asdf": 1231}, {"asd": 142, "asdf": 1243}]
asdf = _.map(asdf, function (row) {
return _.omit(row, ['asd'])
})

Categories

Resources