Recursively loop through objects of object - javascript

I'm trying to write a recursive function to go through an object and return items based off an ID. I can get the first part of this to work, but I'm having a hard time trying to get this function in a recursive manner and could use a fresh set of eyes. The code is below. When you run the snippet, you get an array of 6 items which for the first iteration is what I want, but how can I call my function with the proper parameters to get the nested items? My end goal is to have the all the objects starting with 'Cstm', nested ones too, to be added to the tablesAndValues array. I was trying to model my code after this: Get all key values from multi level nested array JavaScript, but this deals with an array of objects and not an object of objects. Any hint or tips I can get are very appreciated.
JSFiddle: https://jsfiddle.net/xov49jLs/
const response = {
"data": {
"Cstm_PF_ADG_URT_Disposition": {
"child_welfare_placement_value": ""
},
"Cstm_PF_ADG_URT_Demographics": {
"school_grade": "family setting",
"school_grade_code": ""
},
"Cstm_Precert_Medical_Current_Meds": [
{
"med_name": "med1",
"dosage": "10mg",
"frequency": "daily"
},
{
"med_name": "med2",
"dosage": "20mg",
"frequency": "daily"
}
],
"Cstm_PF_ADG_URT_Substance_Use": {
"dimension1_comment": "dimension 1 - tab1",
"Textbox1": "text - tab1"
},
"Cstm_PF_ADG_Discharge_Note": {
"prior_auth_no_comm": "auth no - tab2"
},
"Cstm_PF_ADG_URT_Clinical_Plan": {
"cca_cs_dhs_details": "details - tab2"
},
"container": {
"Cstm_PF_Name": {
"first_name": "same text for textbox - footer",
"last_name": "second textbox - footer"
},
"Cstm_PF_ADG_URT_Demographics": {
"new_field": "mapped demo - footer"
},
"grid2": [
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "knee",
"diagnosis_group_code": "leg"
}
},
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "ankle",
"diagnosis_group_code": "leg"
}
}
]
},
"submit": true
}
};
function getNamesAndValues(data, id) {
const tablesAndValues = [],
res = data;
Object.entries(res).map(([key, value]) => {
const newKey = key.split('_')[0].toLowerCase();
// console.log(newKey) // -> 'cstm'
if (newKey === id) {
tablesAndValues.push({
table: key,
values: value
});
} else {
// I can log value and key and see what I want to push
// to the tablesAndValues array, but I can't seem to get
// how to push the nested items.
// console.log(value);
// console.log(key);
// getNamesAndValues(value, key)
}
});
return tablesAndValues;
}
console.log(getNamesAndValues(response.data, 'cstm'));

To achieve the result with a single push, one can pass the result table to the function when called recursively, but default it to an empty table on the first call. I've also changed .map to .forEach since the return value is not used:
const response = {
"data": {
"Cstm_PF_ADG_URT_Disposition": {
"child_welfare_placement_value": ""
},
"Cstm_PF_ADG_URT_Demographics": {
"school_grade": "family setting",
"school_grade_code": ""
},
"Cstm_Precert_Medical_Current_Meds": [
{
"med_name": "med1",
"dosage": "10mg",
"frequency": "daily"
},
{
"med_name": "med2",
"dosage": "20mg",
"frequency": "daily"
}
],
"Cstm_PF_ADG_URT_Substance_Use": {
"dimension1_comment": "dimension 1 - tab1",
"Textbox1": "text - tab1"
},
"Cstm_PF_ADG_Discharge_Note": {
"prior_auth_no_comm": "auth no - tab2"
},
"Cstm_PF_ADG_URT_Clinical_Plan": {
"cca_cs_dhs_details": "details - tab2"
},
"container": {
"Cstm_PF_Name": {
"first_name": "same text for textbox - footer",
"last_name": "second textbox - footer"
},
"Cstm_PF_ADG_URT_Demographics": {
"new_field": "mapped demo - footer"
},
"grid2": [
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "knee",
"diagnosis_group_code": "leg"
}
},
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "ankle",
"diagnosis_group_code": "leg"
}
}
]
},
"submit": true
}
};
function getNamesAndValues(data, id, tablesAndValues = []) {
const res = data;
Object.entries(res).forEach(([key, value]) => {
const newKey = key.split('_')[0].toLowerCase();
if (newKey === id) {
tablesAndValues.push({
table: key,
values: value
});
} else {
getNamesAndValues( value, id, tablesAndValues); }
});
return tablesAndValues;
}
console.log(getNamesAndValues(response.data, 'cstm'));

You just need to call push to tablesAndValues inside the else statement with the rest operator and pass the value and id as parameters
const response = {
"data": {
"Cstm_PF_ADG_URT_Disposition": {
"child_welfare_placement_value": ""
},
"Cstm_PF_ADG_URT_Demographics": {
"school_grade": "family setting",
"school_grade_code": ""
},
"Cstm_Precert_Medical_Current_Meds": [
{
"med_name": "med1",
"dosage": "10mg",
"frequency": "daily"
},
{
"med_name": "med2",
"dosage": "20mg",
"frequency": "daily"
}
],
"Cstm_PF_ADG_URT_Substance_Use": {
"dimension1_comment": "dimension 1 - tab1",
"Textbox1": "text - tab1"
},
"Cstm_PF_ADG_Discharge_Note": {
"prior_auth_no_comm": "auth no - tab2"
},
"Cstm_PF_ADG_URT_Clinical_Plan": {
"cca_cs_dhs_details": "details - tab2"
},
"container": {
"Cstm_PF_Name": {
"first_name": "same text for textbox - footer",
"last_name": "second textbox - footer"
},
"Cstm_PF_ADG_URT_Demographics": {
"new_field": "mapped demo - footer"
},
"grid2": [
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "knee",
"diagnosis_group_code": "leg"
}
},
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "ankle",
"diagnosis_group_code": "leg"
}
}
]
},
"submit": true
}
};
function getNamesAndValues(data, id) {
const tablesAndValues = [],
res = data;
Object.entries(res).map(([key, value]) => {
const newKey = key.split('_')[0].toLowerCase();
// console.log(newKey) // -> 'cstm'
if (newKey === id) {
tablesAndValues.push({
table: key,
values: value
});
} else {
// I can log value and key and see what I want to push
// to the tablesAndValues array, but I can't seem to get
// how to push the nested items.
// console.log(value);
// console.log(key);
tablesAndValues.push(...getNamesAndValues(value, id))
}
});
return tablesAndValues;
}
console.log(getNamesAndValues(response.data, 'cstm'));
Or in shorter way
function getNamesAndValues2(data, id) {
return Object.entries(data).reduce((arr, [key, value]) => {
arr.push(
...(key.split('_')[0].toLowerCase() === id ? [{ table: key, values: value }] : getNamesAndValues(value, id))
);
return arr
}, []);
}

Here's a working version. I call the main function recursively if the value is either an array or an object. Also passing in the current state of the tallying array each time.
function getNamesAndValues(data, id, tablesAndValues = []) {
const res = data;
Object.entries(res).map(([key, value]) => {
const newKey = key.split('_')[0].toLowerCase();
const item = res[key];
if (newKey === id) {
tablesAndValues.push({
table: key,
values: value
});
}
if(Array.isArray(item)) {
return item.map(el => getNamesAndValues(el, id, tablesAndValues));
}
if(typeof item === 'object') {
return getNamesAndValues(item, id, tablesAndValues);
}
})
return tablesAndValues;
}
console.log(getNamesAndValues(response.data, 'cstm'));

Here's another approach using generators -
const keySearch = (t = [], q = "") =>
filter(t, ([ k, _ ]) => String(k).startsWith(q))
const r =
Array.from
( keySearch(response, "Cstm")
, ([ table, values ]) =>
({ table, values })
)
console.log(r)
[
{
table: 'Cstm_PF_ADG_URT_Disposition',
values: { child_welfare_placement_value: '' }
},
{
table: 'Cstm_PF_ADG_URT_Demographics',
values: { school_grade: 'family setting', school_grade_code: '' }
},
{
table: 'Cstm_Precert_Medical_Current_Meds',
values: [ [Object], [Object] ]
},
{
table: 'Cstm_PF_ADG_URT_Substance_Use',
values: {
dimension1_comment: 'dimension 1 - tab1',
Textbox1: 'text - tab1'
}
},
{
table: 'Cstm_PF_ADG_Discharge_Note',
values: { prior_auth_no_comm: 'auth no - tab2' }
},
{
table: 'Cstm_PF_ADG_URT_Clinical_Plan',
values: { cca_cs_dhs_details: 'details - tab2' }
},
{
table: 'Cstm_PF_Name',
values: {
first_name: 'same text for textbox - footer',
last_name: 'second textbox - footer'
}
},
{
table: 'Cstm_PF_ADG_URT_Demographics',
values: { new_field: 'mapped demo - footer' }
},
{
table: 'Cstm_PF_ADG_COMP_Diagnosis',
values: { diagnosis_label: 'knee', diagnosis_group_code: 'leg' }
},
{
table: 'Cstm_PF_ADG_COMP_Diagnosis',
values: { diagnosis_label: 'ankle', diagnosis_group_code: 'leg' }
}
]
Above, keySearch is simply a specialisation of filter -
function* filter (t = [], test = v => v)
{ for (const v of traverse(t)){
if (test(v))
yield v
}
}
Which is a specialisation of traverse -
function* traverse (t = {})
{ if (Object(t) === t)
for (const [ k, v ] of Object.entries(t))
( yield [ k, v ]
, yield* traverse(v)
)
}
Expand the snippet below to verify the result in your browser -
function* traverse (t = {})
{ if (Object(t) === t)
for (const [ k, v ] of Object.entries(t))
( yield [ k, v ]
, yield* traverse(v)
)
}
function* filter (t = [], test = v => v)
{ for (const v of traverse(t)){
if (test(v))
yield v
}
}
const keySearch = (t = [], q = "") =>
filter(t, ([ k, _ ]) => String(k).startsWith(q))
const response =
{"data":{"Cstm_PF_ADG_URT_Disposition":{"child_welfare_placement_value":""},"Cstm_PF_ADG_URT_Demographics":{"school_grade":"family setting","school_grade_code":""},"Cstm_Precert_Medical_Current_Meds":[{"med_name":"med1","dosage":"10mg","frequency":"daily"},{"med_name":"med2","dosage":"20mg","frequency":"daily"}],"Cstm_PF_ADG_URT_Substance_Use":{"dimension1_comment":"dimension 1 - tab1","Textbox1":"text - tab1"},"Cstm_PF_ADG_Discharge_Note":{"prior_auth_no_comm":"auth no - tab2"},"Cstm_PF_ADG_URT_Clinical_Plan":{"cca_cs_dhs_details":"details - tab2"},"container":{"Cstm_PF_Name":{"first_name":"same text for textbox - footer","last_name":"second textbox - footer"},"Cstm_PF_ADG_URT_Demographics":{"new_field":"mapped demo - footer"},"grid2":[{"Cstm_PF_ADG_COMP_Diagnosis":{"diagnosis_label":"knee","diagnosis_group_code":"leg"}},{"Cstm_PF_ADG_COMP_Diagnosis":{"diagnosis_label":"ankle","diagnosis_group_code":"leg"}}]},"submit":true}}
const result =
Array.from
( keySearch(response, "Cstm")
, ([ table, values ]) =>
({ table, values })
)
console.log(result)

A reasonably elegant recursive answer might look like this:
const getNamesAndValues = (obj) =>
Object (obj) === obj
? Object .entries (obj)
.flatMap (([k, v]) => [
... (k .toLowerCase () .startsWith ('cstm') ? [{table: k, value: v}] : []),
... getNamesAndValues (v)
])
: []
const response = {data: {Cstm_PF_ADG_URT_Disposition: {child_welfare_placement_value: ""}, Cstm_PF_ADG_URT_Demographics: {school_grade: "family setting", school_grade_code: ""}, Cstm_Precert_Medical_Current_Meds: [{med_name: "med1", dosage: "10mg", frequency: "daily"}, {med_name: "med2", dosage: "20mg", frequency: "daily"}], Cstm_PF_ADG_URT_Substance_Use: {dimension1_comment: "dimension 1 - tab1", Textbox1: "text - tab1"}, Cstm_PF_ADG_Discharge_Note: {prior_auth_no_comm: "auth no - tab2"}, Cstm_PF_ADG_URT_Clinical_Plan: {cca_cs_dhs_details: "details - tab2"}, container: {Cstm_PF_Name: {first_name: "same text for textbox - footer", last_name: "second textbox - footer"}, Cstm_PF_ADG_URT_Demographics: {new_field: "mapped demo - footer"}, grid2: [{Cstm_PF_ADG_COMP_Diagnosis: {diagnosis_label: "knee", diagnosis_group_code: "leg"}}, {Cstm_PF_ADG_COMP_Diagnosis: {diagnosis_label: "ankle", diagnosis_group_code: "leg"}}]}, submit: true}}
console .log (getNamesAndValues (response))
.as-console-wrapper {max-height: 100% !important; top: 0}
But this is not as simple as I would like. This code mixes the searching for matches and the test used in that searching together with the formatting of the output. It means that it is a custom function that is both more complex to understand and less reusable than I would like.
I would prefer to use some reusable functions, separating out three feature of this functionality. So, while the following involves more lines of code, I think it makes more sense:
const findAllDeep = (pred) => (obj) =>
Object (obj) === obj
? Object .entries (obj)
.flatMap (([k, v]) => [
... (pred (k, v) ? [[k, v]] : []),
... findAllDeep (pred) (v)
])
: []
const makeSimpleObject = (name1, name2) => ([k, v]) =>
({[name1]: k, [name2]: v})
const makeSimpleObjects = (name1, name2) => (xs) =>
xs .map (makeSimpleObject (name1, name2))
const cstmTest = k =>
k .toLowerCase () .startsWith ('cstm')
const getNamesAndValues = (obj) =>
makeSimpleObjects ('table', 'values') (findAllDeep (cstmTest) (obj))
const response = {data: {Cstm_PF_ADG_URT_Disposition: {child_welfare_placement_value: ""}, Cstm_PF_ADG_URT_Demographics: {school_grade: "family setting", school_grade_code: ""}, Cstm_Precert_Medical_Current_Meds: [{med_name: "med1", dosage: "10mg", frequency: "daily"}, {med_name: "med2", dosage: "20mg", frequency: "daily"}], Cstm_PF_ADG_URT_Substance_Use: {dimension1_comment: "dimension 1 - tab1", Textbox1: "text - tab1"}, Cstm_PF_ADG_Discharge_Note: {prior_auth_no_comm: "auth no - tab2"}, Cstm_PF_ADG_URT_Clinical_Plan: {cca_cs_dhs_details: "details - tab2"}, container: {Cstm_PF_Name: {first_name: "same text for textbox - footer", last_name: "second textbox - footer"}, Cstm_PF_ADG_URT_Demographics: {new_field: "mapped demo - footer"}, grid2: [{Cstm_PF_ADG_COMP_Diagnosis: {diagnosis_label: "knee", diagnosis_group_code: "leg"}}, {Cstm_PF_ADG_COMP_Diagnosis: {diagnosis_label: "ankle", diagnosis_group_code: "leg"}}]}, submit: true}}
console .log (findAllDeep (cstmTest) (response))
.as-console-wrapper {max-height: 100% !important; top: 0}
These are all helper functions of varying degree of reusability:
makeSimpleObject takes two key names, say 'foo', and 'bar' and returns a function which takes a two-element array, say [10, 20] and returns an object matching those up, like {foo: 10, bar: 20}
makeSimpleObjects does the same thing for an array of two-element arrays: makeSimpleObjects('foo', 'bar')([[8, 6], [7, 5], [30, 9]]) //=> [{foo: 8, bar: 6}, {foo: 7, bar: 5}, {foo: 30, bar: 9}].
cstmTest is a simple predicate to test whether a key begins (case-insensitively) with "cstm".
and findAllDeep takes a predicate and returns a function which takes an object and returns an array of two-element arrays, holding the keys/value pairs for any items which match the predicate. (The predicate is supplied both the key and the value; in the current case we only need the key, but it seems sensible for the function to take either.
Our main function, getNamesAndValues, uses findAllDeep (cstmTest) to find the matching values and then makeSimpleObjects ('table', 'values') to convert the result to the final format.
Note that findAllDeep, makeSimpleObject, and makeSimpleObjects are all functions likely to be useful elsewhere. The customization here is only in cstmTest and in the short definition for getNamesAndValues. I would count that as a win.

Related

Javascript Recursion Issue - deeply nested object

I have a function which loops over the keys of an object and looks for sensitive keys like email, key, password, apiKey, secrets, secret and userKey.
If it finds any that have a value, it redacts the value.
However, its failing sometimes with an error like:
"RangeError: Maximum call stack size exceeded"
Whats causing the endless recursion??
const deepObjectRedactor = obj => {
const sensitiveKeys = [
'email',
'key',
'password',
'apiKey',
'secrets',
'secret',
'userKey'
];
Object.keys(obj).forEach(key => {
if (
sensitiveKeys.map(e => e.toLowerCase()).includes(key.toLowerCase()) &&
obj[key]
) {
obj[key] = '**********';
return;
}
if (obj[key] && typeof obj[key] === 'object') {
deepObjectRedactor(obj[key]);
}
});
};
// function invoking the redactor
const customRedactorFormat = info => {
if (info.message && typeof info.message === 'object') {
deepObjectRedactor(info.message);
}
return info;
});
You can write a generic map that works for objects and arrays. And then write redact as a specialization of map -
function map (t, f)
{ switch (t?.constructor)
{ case Array:
return t.map((v, k) => f(k, map(v, f)))
case Object:
return Object.fromEntries(
Object.entries(t).map(([k, v]) => [k, f(k, map(v, f))])
)
default:
return t
}
}
const redact = (t, keys = new Set) =>
map(t, (k, v) => keys.has(k) ? "*****" : v)
const data =
[ { user: 1, cc: 1234, password: "foo" }
, { nested: [ { a: 1, pin: 999 }, { b: 2, pin: 333 } ] }
, { deeply: [ { nested: [ { user: 2, password: "here" } ] } ] }
]
console.log(redact(data, new Set(["cc", "password", "pin"])))
[
{
"user": 1,
"cc": "*****",
"password": "*****"
},
{
"nested": [
{
"a": 1,
"pin": "*****"
},
{
"b": 2,
"pin": "*****"
}
]
},
{
"deeply": [
{
"nested": [
{
"user": 2,
"password": "*****"
}
]
}
]
}
]

How to create the object based on id and merge the inner key into an array JavaScript

I have a list of product each list have product code, parent id and product name. when I click product I am pushing into an array of an object listed below.
[
{"pid":"1","pcode":"van","pname":"mobile"},
{"pid":"1","pcode":"van","pname":"hphone"},
{"pid":"2","pcode":"car","pname":"wphone"},
{"pid":"2","pcode":"car","pname":"email"},
{"pid":"4","pcode":"bus","pname":"sms"}
]
how to create the object group based on id and merge the key3 into an array.
{
"pid":"1",
"details":[
{
"pcode":"van",
"pname":["mobile","hphone"]
}
]
},
{
"pid":"2",
"details":[
{
"pcode":"car",
"pname":["wphone","email"]
}
]
},
{
"pid":"3",
"details":[
{
"pcode":"bus",
"pname":["sms"]
}
]
}
I would use .reduce() for this scenario thus inside you can use .find() to create the desired output. The last pid for bus should be 4 instead of 3 anyway based on the listed array.
Try the following:
const data = [ {"pid":"1","pcode":"van","pname":"mobile"},{"pid":"1","pcode":"van","pname":"hphone"},{"pid":"2","pcode":"car","pname":"wphone"},{"pid":"2","pcode":"car","pname":"email"}, {"pid":"4","pcode":"bus","pname":"sms"}];
const result = data.reduce((a, c) => {
const found = a.find(e => e.pid === c.pid);
if (found) found.details[0].pname.push(c.pname);
else a.push({ pid: c.pid, details: [
{ pcode: c.pcode, pname: [ c.pname ] }
]});
return a;
}, []);
console.log(result);
I hope this helps!
Use .reduce can be done very easily and one for in loop to collect
result. faster result
const data = [
{ pid: "1", pcode: "van", pname: "mobile" },
{ pid: "1", pcode: "something", pname: "hphone" },
{ pid: "1", pcode: "van", pname: "hphone" },
{ pid: "2", pcode: "car", pname: "wphone" },
{ pid: "2", pcode: "car", pname: "email" },
{ pid: "4", pcode: "bus", pname: "sms" }
];
let result = data.reduce((map, cur) => {
if (!map[cur.pid]) {
map[cur.pid] = {
pid: cur.pid,
details: []
}
}
let hasMatch = false
map[cur.pid].details.forEach(x => {
if (x.pcode == cur.pcode) {
hasMatch = true
x.pname.push(cur.pname)
}
})
if (!hasMatch) {
map[cur.pid].details.push({
pcode: cur.pcode,
pname: [cur.pname]
})
}
return map
}, {})
let finalResult = []
for (const r in result) {
finalResult.push(result[r])
}
console.log(JSON.stringify(finalResult, null, 4));
It is possbible to use reduce method to create grouped array by pid. Then we can use map method to assign sequantial pid:
const obj = arr.reduce((a, {pid, pcode, pname}) => {
a[pid] = a[pid] || {pid, details: []};
if (a[pid].details.length === 0)
a[pid].details.push({pcode, pname:[pname]});
else
a[pid].details[0].pname.push(pname);
return a;
}, {})
const result = Object.values(obj).map((v, index) => ({...v, pid: ++index,}))
console.log(result);
An example:
let arr = [
{"pid":"1","pcode":"van","pname":"mobile"},
{"pid":"1","pcode":"van","pname":"hphone"},
{"pid":"2","pcode":"car","pname":"wphone"},
{"pid":"2","pcode":"car","pname":"email"},
{"pid":"4","pcode":"bus","pname":"sms"}
];
const obj = arr.reduce((a, {pid, pcode, pname}) => {
a[pid] = a[pid] || {pid, details: []};
if (a[pid].details.length === 0)
a[pid].details.push({pcode, pname:[pname]});
else
a[pid].details[0].pname.push(pname);
return a;
}, {})
const result = Object.values(obj).map((v, index) => ({...v, pid: ++index,}))
console.log(result);

How to update a property in a deeply nested array of objects of unknown size using Javascript?

I have a large array of objects that I need to recursively loop through and find the object I need to update by its uid and update the data property within that object. This array is of unknown size and shape. The following method works, but I was wondering if there was a "cleaner" way of doing this process.
The following code loops through every object / array until it finds an object with property uid, if this uid equals to the block I need to update, I set the data property of this object. Is there a better way to do this?
Traversal Method
function traverse(x) {
if (isArray(x)) {
traverseArray(x)
} else if ((typeof x === 'object') && (x !== null)) {
traverseObject(x)
} else {
}
}
function traverseArray(arr) {
arr.forEach(function (x) {
traverse(x)
})
}
function traverseObject(obj) {
for (var key in obj) {
// check if property is UID
if (key == "uid") {
// check if this uid is equal to what we are looking for
if (obj[key] == "bcd") {
// confirm it has a data property
if (obj.hasOwnProperty('data')) {
// assign new data to the data property
obj['data'] = newData;
return;
}
}
}
if (obj.hasOwnProperty(key)) {
traverse(obj[key])
}
}
}
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]'
}
// usage:
traverse(this.sections)
Sample data
this.sections = [
{
uid: "abc",
layout: {
rows: [
{
columns: [
{
blocks: [
{
uid: "bcd",
data: {
content: "how are you?"
}
}
]
},
{
blocks: [
{
uid: "cde",
data: {
content: "how are you?"
}
}
]
},
],
},
{
columns: [
{
blocks: [
{
uid: "def",
data: {
content: "how are you?"
}
}
]
}
],
}
]
}
}
]
Is there a better way to handle this? Thanks!
I would recommend starting with a generic object mapping function. It takes an object input, o, and a transformation to apply, t -
const identity = x =>
x
const mapObject = (o = {}, t = identity) =>
Object.fromEntries(Object.entries(o).map(([ k, v ]) => [ k, t(v) ]))
Then build your recursive object mapping function using your shallow function. It takes an object to transform, o, and a transformation function, t -
const recMapObject = (o = {}, t = identity) =>
mapObject // <-- using mapObject
( o
, node =>
Array.isArray(node) // <-- recur arrays
? node.map(x => recMapObject(t(x), t))
: Object(node) === node // <-- recur objects
? recMapObject(t(node), t)
: t(node)
)
Now you build the object transformation unique to your program using our recursive object mapping function. It takes the complex nested data, graph, the uid to match, and a transformation to apply to the matched node, t -
const transformAtUid = (graph = {}, uid = "", t = identity) =>
recMapObject // <-- using recMapObject
( graph
, (node = {}) =>
node.uid === uid // if uid matches,
? { ...node, data: t(node.data) } // then transform node.data,
: node // otherwise no change
)
The above step is important because it detangles the specific logic about node.uid and node.data from the rest of the generic object transformation code.
Now we call our function on the input, data, to transform nodes matching node.uid equal to "bcd" using an example transformation -
const result =
transformAtUid
( data // <-- input
, "bcd" // <-- query
, node => ({ ...node, changed: "x" }) // <-- transformation
)
console.log(result)
Output -
{
"uid": "abc",
"layout": {
"rows": [
{
"columns": [
{
"blocks": [
{
"uid": "bcd",
"data": {
"content": "how are you?",
"changed": "x" // <-- transformed
}
}
]
}
// ...
}
Expand the snippet below to verify the results in your own browser -
const identity = x =>
x
const mapObject = (o = {}, t = identity) =>
Object.fromEntries(Object.entries(o).map(([ k, v ]) => [ k, t(v) ]))
const recMapObject = (o = {}, t = identity) =>
mapObject // <-- using mapObject
( o
, node =>
Array.isArray(node) // <-- recur arrays
? node.map(x => recMapObject(t(x), t))
: Object(node) === node // <-- recur objects
? recMapObject(t(node), t)
: t(node) // <-- transform
)
const transformAtUid = (graph = {}, uid = "", t = identity) =>
recMapObject
( graph
, (node = {}) =>
node.uid === uid
? { ...node, data: t(node.data) }
: node
)
const data =
{uid:"abc",layout:{rows:[{columns:[{blocks:[{uid:"bcd",data:{content:"how are you?"}}]},{blocks:[{uid:"cde",data:{content:"how are you?"}}]},],},{columns:[{blocks:[{uid:"def",data:{content:"how are you?"}}]}],}]}}
const result =
transformAtUid
( data
, "bcd"
, node => ({ ...node, changed: "x" })
)
console.log(JSON.stringify(result, null, 2))
In your original question, I see you have an array of objects to change -
// your original
this.sections = [ {...}, {...}, ... ]
To use recMapObject and transformAtUid with an array of objects, we can use Array.prototype.map -
this.sections = this.sections.map(o => transformAtUid(o, "bcd", ...))
It can be simpler if use a queue:
var sections = [
{
uid: "abc",
layout: {
rows: [
{
columns: [
{
blocks: [
{
uid: "bcd",
data: {
content: "how are you?"
}
}
]
},
{
blocks: [
{
uid: "cde",
data: {
content: "how are you?"
}
}
]
}
]
},
{
columns: [
{
blocks: [
{
uid: "def",
data: {
content: "how are you?"
}
}
]
}
]
}
]
}
}
];
var queue = [];
function trav(ss) {
queue = queue.concat(ss);
while (queue.length > 0) {
tObj(queue.pop());
}
console.log("result", ss);
}
function tObj(obj) {
if (obj.uid === "bcd") {
obj.data = "new data";
} else {
Object.keys(obj).forEach(el => {
if (Array.isArray(obj[el])) {
queue = queue.concat(obj[el]);
} else if (typeof (obj[el]) === "object") {
tObj(obj[el]);
}
});
}
}
trav(sections);

JavaScript - sort 2 object arrays on the same field

In JavaScript I have 2 object arrays that have the same objects but are in a different order. I'm trying to figure out how to sort one array based on the order of the other. There is a unique field they both share (sortField below) I'm just failing on figuring out how to sort with it. Here's an example of my arrays:
sorter array:
[
{
"displayName": "Party",
"sortField": "com.uniqueXbd",
"elementId": "PtyListPanel"
}, {
"displayName": "Group",
"sortField": "com.uniqueARd",
"elementId": "GrpListPaneARd"
}, {
"displayName": "Leader",
"sortField": "com.uniqueEcF",
"elementId": "LeaderListPaneEcF"
}
]
needsSorted array:
[
{
"displayName": "Group",
"sortField": "com.uniqueARd",
"elementId": "GrpListPaneARd"
}, {
"displayName": "Leader",
"sortField": "com.uniqueEcF",
"elementId": "LeaderListPanel"
}, {
"displayName": "Party",
"sortField": "com.uniqueXbd",
"elementId": "PtyListPaneEcF"
}
]
I'm guessing it's going to look something like this?
needsSorted.sort((a, b) => {
if(sorter.sortField...){
return 1
})
Thanks
const output = [];
sortedArray.forEach( sortedItem => {
const matchingItem = unsortedArray.find( unsortedItem => unsortedItem.sortField === sortedItem.sortField );
if(matchingItem){
output.push(matchingItem);
}
});
Since you know the second array is the order you want the items from the first array to be in, you should loop through it. Then find the matching item from the first list, and push it into your output in that order.
You can make a sorting lookup that maps the sort key to the index in the original array. Then in your sort, you can look it up for both objects in the comparison.
This replaces the repeated need to lookup the index in the original array for each comparison with a constant time object lookup so it should be more performant for larger arrays at the expense of the space for the lookup object.
let sortObj = [{"displayName": "Party","sortField": "com.uniqueXbd","elementId": "PtyListPanel"}, {"displayName": "Group","sortField": "com.uniqueARd","elementId": "GrpListPaneARd"}, {"displayName": "Leader","sortField": "com.uniqueEcF","elementId": "LeaderListPaneEcF"}]
let needsSorted = [{"displayName": "Group","sortField": "com.uniqueARd","elementId": "GrpListPaneARd"}, {"displayName": "Leader","sortField": "com.uniqueEcF","elementId": "LeaderListPanel"}, {"displayName": "Party","sortField": "com.uniqueXbd","elementId": "PtyListPaneEcF"}]
let sortLookup = sortObj.reduce((obj, item, idx) => {
obj[item.sortField] = idx
return obj
}, {})
needsSorted.sort((a,b) => sortLookup[a.sortField] - sortLookup[b.sortField])
console.log(needsSorted)
var obj = [
{
"one": 1,
"two": 9
}, {
"one": 3,
"two": 5
}, {
"one": 1,
"two": 2
}
];
var obj = [
{
"one": 1,
"two": 2,
}, {
"one": 1,
"two": 9
}, {
"one": 3,
"two": 5
}
];
obj.sort(function(a, b) {
return a["one"] - b["one"] || a["two"] - b["two"];
});
const sortedIndexes = sorter.map(i => i.sortField);
needsSorted.sort((a, b) => {
const aIndex = sortedIndexes.findIndex((i) => i === a.sortField);
const bIndex = sortedIndexes.findIndex((i) => i === b.sortField);
return aIndex - bIndex;
})
Given that you just want to compare the two arrays and make sure they are still the same, I would go about it differently:
const first = sorted.sort((a, b) => a.localCompare(b))
const second = needsSorting.sort((a, b) => a.localCompare(b))
if (JSON.stringify(first) != JSON.stringify(second)) {
console.log("the array was modified!");
}
const sortOrder = sorted.map(item => item.sortField);
needsSorted.sort((a, b) => {
return sortOrder.indexOf(a.sortField) > sortOrder.indexOf(b.sortField) ? 1 : -1;
});
const fields = sorted.map(x => x.sortField);
const value = x => fields.indexOf(x.sortField);
needSorted.sort((a, b) => value(a) - value(b));
console.log(needSorted);
const sorted = [
{
displayName: "Party",
sortField: "com.uniqueXbd",
elementId: "PtyListPanel"
},
{
displayName: "Group",
sortField: "com.uniqueARd",
elementId: "GrpListPaneARd"
},
{
displayName: "Leader",
sortField: "com.uniqueEcF",
elementId: "LeaderListPaneEcF"
}
];
const needSorted = [
{
displayName: "Group",
sortField: "com.uniqueARd",
elementId: "GrpListPaneARd"
},
{
displayName: "Leader",
sortField: "com.uniqueEcF",
elementId: "LeaderListPanel"
},
{
displayName: "Party",
sortField: "com.uniqueXbd",
elementId: "PtyListPaneEcF"
}
];
const fields = sorted.map(x => x.sortField);
const value = x => fields.indexOf(x.sortField);
needSorted.sort((a, b) => value(a) - value(b));
console.log(needSorted);

Es6 way to convert object key value to one single object

I want to convert all data into one object,
let d = {
"Coupon_Code": "code",
"Coupon_Name": "namie",
"Coupon_Desc": 1000,
"selectedCity": [
{
"Coupon_City_Name": "xyz"
}
],
"selectedCategory": [
{
"Coupon_Category_Name": "Shopping"
}
],
"selectedCompany": [
{
"Coupon_Company_Name": "Shopper Stop"
}
],
"selectedState": [
{
"Coupon_State_Name": "abc"
}
],
"Coupon_Date": "2222-02-22",
}
i tried some methods of Object like keys , entries but dont no what to use.
Final output should be
let d = {
Coupon_Code: "code",
Coupon_Name: "namie",
Coupon_Desc: 1000,
Coupon_City_Name: "xyz",
Coupon_Category_Name: "Shopping",
Coupon_Company_Name: "Shopper Stop",
Coupon_State_Name: "abc",
Coupon_Date: "2222-02-22",
};
what's the best and optimum way to have above result using Venila Js and Es6
Reduce the entries of the original object. If the entry's value is an array merge the 1st element, if not merge the original key and value. You can merge the properties into the object using object spread:
const data = {"Coupon_Code":"code","Coupon_Name":"namie","Coupon_Desc":1000,"selectedCity":[{"Coupon_City_Name":"xyz"}],"selectedCategory":[{"Coupon_Category_Name":"Shopping"}],"selectedCompany":[{"Coupon_Company_Name":"Shopper Stop"}],"selectedState":[{"Coupon_State_Name":"abc"}],"Coupon_Date":"2222-02-22"};
const result = Object.entries(data)
.reduce((r, [k, v]) => ({
...r,
...Array.isArray(v) ? v[0] : { [k]: v }
}), {});
console.log(result);
You can use Array.reduce and Object.entries
let d = {"Coupon_Code":"code","Coupon_Name":"namie","Coupon_Desc":1000,"selectedCity":[{"Coupon_City_Name":"xyz"}],"selectedCategory":[{"Coupon_Category_Name":"Shopping"}],"selectedCompany":[{"Coupon_Company_Name":"Shopper Stop"}],"selectedState":[{"Coupon_State_Name":"abc"}],"Coupon_Date":"2222-02-22"};
d = Object.entries(d).reduce((a,[k,v]) => {
// If the value is an array, iterate over it to merge into the resultant object
if(Array.isArray(v)) Object.assign(a, ...v)
else Object.assign(a, {[k]:v}) // if it is not an array, merge into resultant object
return a;
}, {});
console.log(d);
You could take a recursive approach.
const
fn = o => Object.assign(...Object.entries(o).map(([k, v]) => Array.isArray(v) ? Object.assign(...v.map(fn)) : { [k]: v })),
d = { Coupon_Code: "code", Coupon_Name: "namie", Coupon_Desc: 1000, selectedCity: [{ Coupon_City_Name: "xyz" }], selectedCategory: [{ Coupon_Category_Name: "Shopping" }], selectedCompany: [{ Coupon_Company_Name: "Shopper Stop" }], selectedState: [{ Coupon_State_Name: "abc" }], Coupon_Date: "2222-02-22" },
result = fn(d);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A possible iterative solution is:
function flatten(obj) {
let r = {}
for (let [key, value] of Object.entries(obj)) {
if (Array.isArray(value)) {
Object.assign(r, value[0]);
} else {
Object.assign(r, {[key]: value});
}
}
return r;
}
Something like this:
const d = { Coupon_Code: "code", Coupon_Name: "namie", Coupon_Desc: 1000, selectedCity: [{ Coupon_City_Name: "xyz" }], selectedCategory: [{ Coupon_Category_Name: "Shopping" }], selectedCompany: [{ Coupon_Company_Name: "Shopper Stop" }], selectedState: [{ Coupon_State_Name: "abc" }], Coupon_Date: "2222-02-22" };
function toSingleObj(obj) {
var result = {};
Object.entries(obj).forEach(([key,value]) => {
if (Array.isArray(value)) {
Object.entries(value[0]).forEach(([k,v]) => {
result[k] = v;
});
} else {
result[key] = value;
}
});
return result;
}
console.log("Result: ", toSingleObj(d));

Categories

Resources