Javascript function that merges objects based on values of the keys and restructures them based on values of the keys - javascript

I am trying to write a javascript function that receives at least two parameters - array of objects and empty nested object.
Array of objects contain objects that are not nested. All of them have same keys, but some keys might have different values. Each key name is something like "primary key" on certain level of nesting. Where the key should hold place as "primary key" is defined by empty nested object.
Empty nested object defines which key is "primary key" for which level of nesting. It defines the structure we need the final merged object to be.
The function should find and merge together all objects in array of objects, that have same value for the key thats first in empty nested object AND is type of object. However, because the objects hold other keys that have the same name but might have different values, if merged instantly, we would lose the different values.
So it takes value of last key that was found in empty nested object and was type of object, and creates a new key with name of that value and makes it a object. Inside it, it creates the name of key that this object is from, creates the key with same name and assigns it that value.
Then it keeps doing the same, based on empty nested object. In other words, the function is keep looking on empty nested object and checks if on the current level there is any other key thats type of object, before merging all objects together. It is merging the keys with same values from the bottom up.
All keys dont necessarily have to be "primary keys" on some level. These keys are present inside "DATA" level. In "DATA" might also appear a key that was a "primary key" on some level. Again, this is defined by empty nested object.
Example of empty nested object:
{
PERSON: {
CRT: {
CRT_VALUE: {
CRT: null,
CODE: {
CODE_VALUE: {
CODE: null,
DATA: {
CODE: {
VALUE: null,
META: null,
NAME: null
},
NAME: {
VALUE: null,
META: null,
NAME: null
},
SUM: {
VALUE: null,
META: null,
NAME: null
},
CONTEXT: {
VALUE: null,
META: null,
NAME: null
}
}
}
}
}
}
}
}
Example of array of objects:
[
{
PERSON: "21",
CODE: "CMSS",
CONTEXT: "",
NAME: "SALARY",
CRT: "Common",
SUM: 162000
},
{
PERSON: "21",
CODE: "DOS",
CONTEXT: "",
NAME: "TAX",
CRT: "Common",
SUM: 192000
},
{
PERSON: "21",
CODE: "UPL",
CONTEXT: "",
NAME: "WAGE",
CRT: "Other",
SUM: 255000
},
{
PERSON: "22",
CODE: "CMSS",
CONTEXT: "",
NAME: "SALARY",
CRT: "Common",
SUM: 150000
}
]
The final array of objects should be merged based on values of the keys and based on empty nested object.
For given examples, the final array of objects should look like this:
[
{
PERSON: "21",
CRT: {
"Common": {
CRT: "Common",
CODE: {
"CMSS": {
CODE: "CMSS",
DATA: {
NAME: {
VALUE: "SALARY",
META: undefined,
NAME: "NAME",
},
SUM: {
VALUE: 162000,
META: undefined,
NAME: "SUM",
},
CONTEXT: {
VALUE: "",
META: undefined,
NAME: "CONTEXT",
},
CODE: {
VALUE: "CMSS",
META: undefined,
NAME: "CODE",
}
}
},
"DOS": {
CODE: "DOS",
DATA: {
NAME: {
VALUE: "TAX",
META: undefined,
NAME: "NAME",
},
SUM: {
VALUE: 192000,
META: undefined,
NAME: "SUMA",
},
CONTEXT: {
VALUE: "",
META: undefined,
NAME: "KONTEXT",
},
CODE: {
VALUE: "DOS",
META: undefined,
NAME: "CODE",
}
}
}
},
},
"Other": {
CRT: "Other",
CODE: {
"UPL": {
CODE: "UPL",
DATA: {
NAME: {
VALUE: "WAGE",
META: undefined,
NAZEV: "NAME",
},
SUM: {
VALUE: 255000,
META: undefined,
NAZEV: "SUM",
},
CONTEXT: {
VALUE: "",
META: undefined,
NAME: "CONTEXT",
},
CODE: {
VALUE: "UPL",
META: undefined,
NAME: "CODE",
}
}
}
}
}
}
},
{
PERSON: "22",
CRT: {
"Common": {
CRT: "Common",
CODE: {
"CMSS": {
CODE: "CMSS",
DATA: {
NAME: {
VALUE: "SALARY",
META: undefined,
NAME: "NAME",
},
SUM: {
VALUE: 150000,
META: undefined,
NAME: "SUM",
},
CONTEXT: {
VALUE: "",
META: undefined,
NAME: "CONTEXT",
},
CODE: {
VALUE: "CMSS",
META: undefined,
NAME: "CODE",
}
}
}
}
}
}
}
]
The function should be generic, so it works for any given array of objects, and any given empty nested object - however, empty nested object must always have defined structure by the key names.
The array of objects must (will) always contain all the needed keys based on empty nested object.
What I tried and did not work:
const mergeObjectsByKey = (array, key, newObject = {}, level = 1) => {
let newObjects = []
array.forEach(obj => {
let currentObject = newObject;
for (let k of Object.keys(key)) {
if (typeof key[k] === 'object') {
if (obj.hasOwnProperty(k)) {
if (!currentObject.hasOwnProperty(obj[k])) {
currentObject[obj[k]] = {};
}
currentObject = currentObject[obj[k]];
mergeObjectsByKey([obj], key[k], currentObject, level + 1);
}
} else {
if (Object.keys(key)[Object.keys(key).length - 1] === k) {
currentObject["HODNOTA"] = obj[k];
currentObject["META"] = null;
currentObject["NAZEV"] = k;
} else {
if (!currentObject.hasOwnProperty(k)) {
currentObject[k] = obj[k];
}
}
}
}
newObjects.push(newObject);
});
return newObjects;
}
newObject - the object that will be returned as the final object
level - a number that keeps track of the current level of nesting in the key object. It is used to determine if the current key in the empty nested object is a parent or child key
The function loops through each key in the empty nested object. If the current key is an object, it loops through each object in the array of objects and checks if the object has a key with the same name as the current key. If it does, it calls the function recursively with the current object, current key and current level+1 as the parameters. By doing this, it will check the next level of key object and compare it with the next level of the array of objects.
In the next level, it will again check if the key is an object or not. If it is an object, it will check if the key is already present in the current object or not. If it is not present, it will create a new property with the key and make it equal to an empty object.
Then it will call the function recursively with the current object, current key, and current level+1 as the parameters. If the key is not an object, it will check if it is the last key of the key object or not.
If it is the last key, it will add three properties HODNOTA, META and NAZEV to the current object.
If it is not the last key, it will add the key to the current object and make it equal to an array of values of the same key in the array of objects.
What I expected:
the function will return final merged and structured objects in an array
What I got:
array of objects of objects. Each object in array contain same number of objects, and all these objects in the array are same.
Inside objects of the array, each object has the name of value of "PERSON" that was used to merging.
In each PERSON object, there are objects named by all CRT values found for that PERSON value. Each CRT object is then empty.
I can see that partially it was merged succesfully, but from CRT the merging failed and lost the data.

Not sure it's perfect, but it does something close. The technique here starts with Object.fromEntries(Object.entries(obj).map(...)) to modify the keys and values and build a new object based on the template object.
const TPL = {PERSON:null,CRT:{CRT_VALUE:{CRT:null,CODE:{CODE_VALUE:{CODE:null,DATA:{CODE:{VALUE:null,META:null,NAME:null},NAME:{VALUE:null,META:null,NAME:null},SUM:{VALUE:null,META:null,NAME:null},CONTEXT:{VALUE:null,META:null,NAME:null}}}}}}};
const values = [{PERSON:"21",CODE:"CMSS",CONTEXT:"",NAME:"SALARY",CRT:"Common",SUM:162000},{PERSON:"21",CODE:"DOS",CONTEXT:"",NAME:"TAX",CRT:"Common",SUM:192000},{PERSON:"21",CODE:"UPL",CONTEXT:"",NAME:"WAGE",CRT:"Other",SUM:255000},{PERSON:"22",CODE:"CMSS",CONTEXT:"",NAME:"SALARY",CRT:"Common",SUM:150000}];
function fillTpl(tpl, values, parentKey = '') {
return Object.fromEntries(Object.entries(tpl).map(([k, v]) => {
if (k.endsWith('_VALUE')) k = values[k.slice(0, -6)];
if (v == null) v = (k === 'VALUE') ? values[parentKey] : values[k];
else v = fillTpl(v, values, k);
return [k, v];
}));
}
console.log(values.map(vals => fillTpl(TPL, vals)));
If you want to add new properties to the object, you can do like Object.fromEntries(Object.entries(obj).flatMap(([k, v]) => [[k, v], [k+'_2', v]]))

Related

Looping through an object only returns the first object

I am trying to reshape some data to be used in a table. To do this I am mapping over an array and then looping over an object inside of it to create a new object with the data I want in it. The issue Im having is if the object contains more than one object inside of it I only get a new object for the first item.
I am trying to get a new object for each of the objects held inside changedProperties
Can anyone help? / Explain?
Here is a working example:
const MOCK_DATA = [
{
triggeredByID: "d5ae18b7eb6f",
triggeredByName: "name",
changedProperties: {
LastName: {
__oldValue: "Mallory",
__newValue: "Mallorie",
},
Suffix: {
__oldValue: "DO",
__newvValue: "NP",
},
},
createdAt: "2022-06-01T10:20:21.329337652Z",
},
{
triggeredByID: "d5ae18b7eb6f",
triggeredByName: "John",
changedProperties: {
State: {
__oldValue: ["TX", "AL"],
__newValue: ["TX", "AL", "FL"],
},
City: {
__oldValue: "Austin",
__newValue: "San Antonio",
},
},
createdAt: "2022-06-01T10:20:21.329337652Z",
},
];
const changedProperties = MOCK_DATA.map((item, idx) => {
for (const [key, value] of Object.entries(item.changedProperties)) {
return {
field: key,
old: item.changedProperties[key].__oldValue,
new: item.changedProperties[key].__newValue,
name: item.triggeredByName,
createdAt: item.createdAt,
};
}
});
console.log(changedProperties)
Yes, if you return inside a for-loop, its scoped to the function. Meaning you will return from the entire function on the first iteration of your loop. Try this instead:
const changedProperties = MOCK_DATA.map((item, idx) => {
return Object.entries(item.changedProperties).map(([key, value]) => {
return {
field: key,
old: item.changedProperties[key].__oldValue,
new: item.changedProperties[key].__newValue,
name: item.triggeredByName,
createdAt: item.createdAt,
};
}
});
console.log(changedProperties)

Return true if an array within an array contains specific key

I have the following the object:
items: [
{
object_a {
id: "1",
value: "somevalue1"
}
},
{
object_b {
id: "2"
value: "somevalue2"
items:[
{
nested_object_a {
id: "3"
value: "somevalue3"
},
nested_object_b {
id: "4"
value: "somevalue4"
},
}
]
}
},
]
I can check if the value key exists in the initial items array:
items.some(item => item.hasOwnProperty("value"));
To see if the value key exists in the nested object_b item array I can do the following:
items[1].object_b.items.some(item => item.hasOwnProperty("value"));
Instead of specifying which number in the array to look in I need to make the final expression dynamic. I'm certain I need to do a find or a filter on the top level items, but I've had no luck with it so far.
I found that using an every function on the items allowed me to return the boolean value I required form the array. The problem this created is any array item that didn't have the object_b key on was returning null and breaking my app. It turns out I needed to use an optional chaining operator to get around this (.?).
Thanks to #ikhvjs for linking that stackedoverflow article.
Working code:
items.every(item => {
item.object_b?.items.some(item => item.hasOwnProperty("value"));
});

Trying to create array of strings to use in a destructuring function for removing object properties, only single string value works

I have an array of objects as part of a data response that I am grouping together using lodash's groupBy via each object's groupName key.
Some of the items that come back have a groupName value of null, undefined or an empty string and lodash creates separate groups for each of those values.
I combine all of the falsey groups into a single group name "Uncategorized" and attempt to remove the original falsey groups to only return "Uncategorized" and all other truthy groups.
The problem I'm running into is that I'm trying to use the rest operator to remove the original falsy objects with undefined, null, and empty string keys by assigning them to a variable like let groupKeysToRemove = ['undefined', 'null', ''] and then trying to remove them like let { [groupKeysToRemove]: removed, ...groups } = initialGroups; but it returns the same Object with nothing removed. I'm not sure if my syntax is wrong or what but I am stumped.
Code via sandbox:
const resources = [
{
groupName: undefined,
name: "color organizer"
},
{
groupName: null,
name: "Bart_Simpson_200px"
},
{
groupName: "Spread Sheets",
name: "Backflow"
},
{
groupName: "Spread Sheets",
name: "220px-Marge_Simpson"
},
{
groupName: "",
name: "212px-Homer_Simpson_2006"
},
{
groupName: "Spread Sheets",
name: "Product 6"
},
{
groupName: "Warranties",
name: "Warranty Bart Simpson"
},
{
groupName: "Warranties",
name: "Warranty Product 2"
},
{
groupName: "Warranties",
name: "Warranty Product 3"
}
];
let initialGroups = groupBy(resources, "groupName");
let uncategorizedGroups = [];
uncategorizedGroups.push(...initialGroups[undefined], ...initialGroups[null], ...initialGroups[""]);
const renameGroups = uncategorizedGroups.map((object) => {
object.groupName = "Uncategorized";
return object;
});
const renamedGroups = groupBy(renameGroups, "groupName");
console.log('RENAMED GROUPS: ', renamedGroups)
const groupKeysToRemove = "undefined"
let { [groupKeysToRemove]: removed, ...groups } = initialGroups;
groups = { ...groups, ...renamedGroups };
Think of the brackets syntax [] for the destructing operation as an index to a property of an object, not an array that you pass in. It's analogous to calling for example obj["a"] vs obj.a to access the a field on obj.
So knowing this, you need to pass in 3 arguments to extract the values that you want to remove. For null and undefined I had to put them in separate variables, it wasn't working when putting them directly in the brackets:
const nullKey = null;
const undefinedKey = undefined;
let {
[nullKey]: nullGroup,
[undefinedKey]: undefinedGroup,
[""]: emptyStringGroup,
...groups } = initialGroups;
groups = { ...groups, ...renamedGroups };
console.log("initialGroups: ", initialGroups);
console.log("GROUPS: ", groups);
console.log("null group", nullGroup)
console.log("undef group", undefinedGroup)
console.log("emptyStringGroup group", emptyStringGroup)

Best way to to pass the output of a function to child components in Vue

I have a parent component with a data object config as below:
data() {
return {
config: {
Groups: [
{
name: "A",
Types: [
{ mask: 1234, name: "Alice", type: 1},
{ mask: 5678, name "Bob", type: 1},
]
},
{
name: "B",
Types: [
{ mask: 9876, name: "Charlie", type: 2},
{ mask: 5432, name "Drake", type: 2},
]
}
],
},
Defaults: {
dummyBoolean: false,
dummyNumber: 1
}
}
}
}
There are also 2 child components that for each of them, I want to pass the Types array (within each elements of the Groups object) if each_element.name == child component's name.
What I've done so far is having a computed function for each of the components as follows (which is highly inefficient):
computed: {
dataSender_A() {
let array= []
this.config.Groups.forEach( element => {
if (element.name === "A") array = element.Types
});
return array
},
dataSender_B() {
let array= []
this.config.Groups.forEach( element => {
if (element.name === "B") array = element.Types
});
return array
},
}
I'm looking for a better alternative to make this happen (as I might have more child components) and two approaches I tried so far have failed.
Having only one computed function that takes the component's name as argument and can be passed like <child-component-A :types="dataSender('A')" /> <child-component-B :types="dataSender('B')" /> (As it throws error dataSender is not a function)
computed: {
dataSender: function(groupName) {
let array= []
this.config.Groups.forEach( element => {
if (element.name === groupName) array = element.Types
});
return array
},
}
Having the above function in methods and pass that as props to child components (As it passes the function itself, not the outputted array)
I'd appreciate any help on this.
The computed properties don't accept parameters that are involved in the calculation, In this case you could just use a method like :
methods: {
dataSender: function(groupName) {
let array= []
this.config.Groups.forEach( element => {
if (element.name === groupName) array = element.Types
});
return array
},
}

Array.forEach() and Array.slice() together doesn't work correctly [duplicate]

This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 6 years ago.
it's supposed to Array.slice() let me to make a copy of an array, and then I can modify that copy without modifying the original array, but when I use Array.forEach() over the copy for delete some values this values also are removed from the original array. Does anybody have an idea why this happens?
Here is the code that I've used:
var originalArray = [
{ id: 1, name: 'Sales', datasources: [
{ id:1 , name: 'datasource1', fields: [] },
{ id:2 , name: 'datasource2', fields: [] },
] },
{ id: 4, name: 'Accounts', datasources: [
{ id:3 , name: 'datasource3', fields: [] },
{ id:4 , name: 'datasource4', fields: [] },
] },
{ id: 123, name: 'my datasources', datasources: [
{ id:1 , name: 'datasource1', fields: [] },
{ id:2 , name: 'datasource2', fields: [] },
{ id:3 , name: 'datasource3', fields: [] },
{ id:4 , name: 'datasource4', fields: [] },
] },
{ id: 12, name: 'shared datasources', datasources: [
{ id:13 , name: 'myshared datasource', fields: [] },
{ id:16 , name: 'hello test', fields: [] },
] },
];
var copyOfOriginalArray = originalArray.slice();
copyOfOriginalArray.forEach((folder, index) => {
folder.datasources = folder.datasources.filter((o) => { return o.name.trim().toLowerCase().includes('hello'); });
});
JSON.stringify(originalArray);
JSON.stringify(copyOfOriginalArray);
Acording to this definition.
The slice() method returns a shallow copy of a portion of an array into a new array object.
Shallow copy is a bit-wise copy of an object. A new object is created that has an exact copy of the values in the original object. If any of the fields of the object are references to other objects, just the reference addresses are copied i.e., only the memory address is copied.
for deep copying any object in javascript you can use this function:
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] = deepCopy(oldObj[i]);
}
}
return newObj;
}
Slice will create a copy of the array itself, but it will not clone the objects in the array (those will still be references).
You will need to recursively clone your array and its contents or use something like Lodash's cloneDeep

Categories

Resources