Javascript Recursion Issue - deeply nested object - javascript

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": "*****"
}
]
}
]
}
]

Related

cannot add property 1 , onject is not extensible in react

anyone has an idea what causes the ff issue ? I cannot insert new object to a key object in my arrays of objects for example I wanna insert new email to emails at index 1 when I push to that it causes error cannot add property 1 , onject is not extensible in .
Ideads and help would be much appreciated. Thank.s
#code
const addEmail = (id: number) => {
console.log('...RegionalList',RegionalList)
const regionalList = [...RegionalList];
const index = regionalList
.map((prop: IRegionalList) => prop.id)
.indexOf(id);
console.log('regionalList[index].emails' , regionalList[index].emails)
regionalList[index].emails.push({
emailAddress: '',
firstName: '',
lastName: '',
id: Math.floor(Math.random() * 999),
fetching: false,
});
setRegionalList(regionalList);
};
#object where I am inserting on regionalist arrays of object the value of this variable const regionalList = [...RegionalList];
[
{
"id": 4,
"name": "Associate Director of Construction Ops",
"column": "associateDirectorofConstructionOps",
"emails": [
{
"id": 79,
"emailAddress": "crawform#raw.com",
"firstName": "James",
"lastName": "Crawford"
}
]
},
{
"id": 5,
"name": "CAM Manager",
"column": "camManager",
"emails": [
{
"id": 77,
"emailAddress": "jenn.jones4#test.com",
"firstName": "Jennifer",
"lastName": "Jones"
}
]
},
]
#another snippet
const setEmailValue = (event: any, regionalId: number, index: number) => {
setRegionalList((prevState: IRegionalList[]) => {
const newState = prevState.map((prop: IRegionalList) => {
if (prop.id === regionalId) {
prop.emails[index] = { emailAddress: event.target.value, id: null };
return { ...prop };
}
return prop;
});
return newState;
});
}
#another snippet
useEffect(() => {
if (selectedRow) {
console.log('selectedRow' , selectedRow)
// Set selected row data
setData({
regionName: selectedRow['regionName'],
marketName: selectedRow['marketName'],
subRegionName: selectedRow['subRegionName'],
});
let regional = [...RegionalList];
for (const k in selectedRow) {
regional.map((prop: IRegionalList) => {
if (prop.column === k) {
prop.emails = selectedRow[k] ? selectedRow[k] : []
}
})
}
console.log('regional:', regional);
setRegionalList(regional);
}
}, [selectedRow]);
As you cannot mutate the state as in your code above, you have to create and return a new array, so try:
const addEmail = (id: number) => {
setRegionalList(list => list.map(item => {
if (item.id === id) {
return {
...item,
emails: [
...item.emails,
{
emailAddress: '',
firstName: '',
lastName: '',
id: Math.floor(Math.random() * 999),
fetching: false,
}
]
}
}
return item;
}))
};

Separating (n) keys from array of objects into a single array with keys names

I need to perform filter in the array of objects to get all the keys. Although, whenever there is a obj inside of that key, I would need to get the key name and concat with the key name from the obj, so for example:
const data = [ id: 5, name: "Something", obj: { lower: True, higher: False } ]
result = ["id", "name", "obj.lower", "obj.higher"]
I could manage to do the above code, but, if there is more objs inside the data, I would need to keep adding a if condition inside of my logic, I would like to know if there is any other way, so it doesn't matter how many objects I have inside the objects, It will concat always.
The code I used from the above mention:
const itemsArray = [
{ id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
{ id: 2, item: "Item 002", obj: { name: 'Nilton002', message: "Free002", obj2: { test: "test002" } } },
{ id: 3, item: "Item 003", obj: { name: 'Nilton003', message: "Free003", obj2: { test: "test003" } } },
];
const csvData = [
Object.keys(itemsArray[0]),
...itemsArray.map(item => Object.values(item))
].map(e => e.join(",")).join("\n")
// Separating keys
let keys = []
const allKeys = Object.entries(itemsArray[0]);
for (const data of allKeys) {
if (typeof data[1] === "object") {
const gettingObjKeys = Object.keys(data[1]);
const concatingKeys = gettingObjKeys.map((key) => data[0] + "." + key);
keys.push(concatingKeys);
} else {
keys.push(data[0])
}
}
//Flating
const flattingKeys = keys.reduce((acc, val: any) => acc.concat(val), []);
What I would like to achieve, lets suppose I have this array of object:
const data =
[
{ id: 10, obj: {name: "Name1", obj2: {name2: "Name2", test: "Test"}}}
...
]
Final result = ["id", "obj.name", "obj.obj2.name2", "obj.obj2.test"]
OBS: The first obj contains all the keys I need, no need to loop through other to get KEYS.
I would like to achieve, all the keys from the first object of the array, and if there is objects inside of objects, I would like to concat the obj names (obj.obj2key1)
You could map the key or the keys of the nested objects.
const
getKeys = object => Object
.entries(object)
.flatMap(([k, v]) => v && typeof v === 'object'
? getKeys(v).map(s => `${k}.${s}`)
: k
),
getValues = object => Object
.entries(object)
.flatMap(([k, v]) => v && typeof v === 'object'
? getValues(v)
: v
),
data = { id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
keys = getKeys(data),
values = getValues(data);
console.log(keys);
console.log(values);
.as-console-wrapper { max-height: 100% !important; top: 0; }
something like this
const itemsArray = [
{ id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
{ id: 2, item: "Item 002", obj: { name: 'Nilton002', message: "Free002", obj2: { test: "test002" } } },
{ id: 3, item: "Item 003", obj: { name: 'Nilton003', message: "Free003", obj2: { test: "test003" } } },
];
const item = itemsArray[0];
const getAllKeys = (obj, prefix=[]) => {
if(typeof obj !== 'object'){
return prefix.join('.')
}
return Object.entries(obj).flatMap(([k, v]) => getAllKeys(v, [...prefix, k]))
}
console.log(getAllKeys(item))
The OP solution can be simplified by accepting a prefix param (the parent key) and a results param (defaulted to [] and passed into the recursion) to do the flattening...
let obj = { key0: 'v0', key1: { innerKey0: 'innerV0', innerInner: { deeplyNested: 'v' } }, key2: { anotherInnerKey: 'innerV' } }
function recursiveKeys(prefix, obj, result=[]) {
let keys = Object.keys(obj);
keys.forEach(key => {
if (typeof obj[key] === 'object')
recursiveKeys(key, obj[key], result);
else
result.push(`${prefix}.${key}`)
});
return result;
}
console.log(recursiveKeys('', obj))
function getKeys(obj) {
return Object.keys((typeof obj === 'object' && obj) || {}).reduce((acc, key) => {
if (obj[key] && typeof obj[key] === 'object') {
const keys = getKeys(obj[key]);
keys.forEach((k) => acc.add(`${key}.${k}`));
} else {
acc.add(key);
}
return acc;
}, new Set());
}
// accumulate the keys in a set (the items of the array may
// have different shapes). All of the possible keys will be
// stored in a set
const s = itemsArray.reduce(
(acc, item) => new Set([...acc, ...getKeys(item)]),
new Set()
);
console.log('Keys => ', Array.from(s));
You can use recursion as follows. Since typeof([1,3,5]) is object, we also have to confirm that value is not an array, !Array.isArray(value):
const obj = { id: 10, obj: {name: "Name1", obj2: {name2: "Name2", test: "Test"}}};
const getKeys = (o,p) => Object.entries(o).flatMap(([key,value]) =>
typeof(value) === 'object' && !Array.isArray(value) ?
getKeys(value, (p?`${p}.`:"") + key) :
(p ? `${p}.`: "") + key
);
console.log( getKeys(obj) );

Recursively loop through objects of object

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.

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);

Relate and merge array of same Department

I am working on an application where I need to get combine the object of same department based on the
conditions provided in the second Array and attach the relation to the object.
let inArr1 = [{"D1D2":"AND"},{"D3D4":"OR"}]
let inArr2 =[{"ID":"1","NAME":"KEN","DEPT1":"CSE"},
{"ID":"2","NAME":"MARK","DEPT2":"IT"},
{"ID":"3","NAME":"TOM","DEPT3":"ECE"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB"},
{"ID":"5","NAME":"TIM","DEPT5":"SEC"}
]
Output
outArr ={
[{"ID":"1","NAME":"KEN","DEPT1":"CSE","REL":"AND"},
{"ID":"2","NAME":"MARK","DEPT2":"IT","REL":"AND"}], //Arr1
[{"ID":"3","NAME":"TOM","DEPT3":"ECE","REL":"OR"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB","REL":"OR"}], //Arr2
[{"ID":"5","NAME":"TIM","DEPT5":"SEC"}] //Arr3
}
Code:
let condArr=[],outArr,i=1;
inArr1.forEach(condt => {
let dept = Object.keys(condt)[0];
let tmparr = dept.split("D");
tmparr.shift()
condArr.push(tmparr)
});
inArr2.forEach(condt => {
if(condArr.includes(inArr2.D+i)){
i++;
outArr.push(inArr2);
}
});
Your code has a bit confused logic, i would suggest rather this
let inArr1 = [{"D1D2":"AND"},{"D3D4":"OR"},{"D5D6":"AND"}]
let inArr2 =[{"ID":"1","NAME":"KEN","DEPT1":"CSE"},
{"ID":"2","NAME":"MARK","DEPT2":"IT"},
{"ID":"3","NAME":"TOM","DEPT3":"ECE"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB"},
{"ID":"5","NAME":"TIM","DEPT5":"SEC"},
{"ID":"6","NAME":"TLA","DEPT6":"SEC"},
]
// first lets create object of ids as keys and conditions as values
const [keys, conditions] = inArr1.reduce((agg, cond, index) => {
Object.entries(cond).forEach(([key, value]) => {
key.split('D').forEach(v => { if (v) agg[0][v] = { value, index }})
agg[1].push([])
})
return agg
}, [{}, []]) // {1: "AND", 2: "AND", 3: "OR", 4: "OR"}
conditions.push([])
// and now just map over all elements and add condition if we found id from the keys
inArr2.forEach(item => {
const cond = keys[item.ID]
if (cond) conditions[cond.index].push({...item, REL: cond.value})
else conditions[conditions.length - 1].push(item)
})
const res = conditions.filter(v => v.length)
console.log(res)
You could store the goups by using the ID and use new objects.
let inArr1 = [{ D1D2: "AND" }, { D3D4: "OR" }],
inArr2 = [{ ID: "1", NAME: "KEN", DEPT1: "CSE" }, { ID: "2", NAME: "MARK", DEPT2: "IT" }, { ID: "3", NAME: "TOM", DEPT3: "ECE" }, { ID: "4", NAME: "SHIV", DEPT4: "LIB" }, { ID: "5", NAME: "TIM", DEPT5: "SEC" }],
groups = inArr1.reduce((r, o) => {
Object.entries(o).forEach(([k, REL]) => {
var object = { REL, group: [] };
k.match(/[^D]+/g).forEach(id => r[id] = object);
});
return r;
}, {}),
grouped = inArr2.reduce((r, o) => {
var { REL, group } = groups[o.ID] || {};
if (group) {
if (!group.length) r.push(group);
group.push(Object.assign({}, o, { REL }));
} else {
r.push([o]);
}
return r;
}, []);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
can try other solution:
let inArr1 = [{ D1D2: "AND" }, { D3D4: "OR" }, { D6D7: "XOR" }];
let inArr2 = [
{ ID: "1", NAME: "KEN", DEPT1: "CSE" },
{ ID: "2", NAME: "MARK", DEPT2: "IT" },
{ ID: "3", NAME: "TOM", DEPT3: "ECE" },
{ ID: "4", NAME: "SHIV", DEPT4: "LIB" },
{ ID: "5", NAME: "TIM", DEPT5: "SEC" },
{ ID: "9", NAME: "BAR", DEPT5: "XYZ" },
{ ID: "6", NAME: "FOO", DEPT5: "XYZ" },
];
let unmatchedArr = []
let matchedArr = inArr2.reduce((acc, obj) => {
// getting index matched from inArr1 objects key
const indexMatched = getIndexMatch(obj.ID);
// creating index if not exists
if (!acc[indexMatched] && indexMatched !== null) acc[indexMatched] = [];
// if some index matched it merge current obj with DEL property with inArr1[indexMatched] key => value
return indexMatched !== null
? acc[indexMatched].push({
...obj,
DEL: inArr1[indexMatched][Object.keys(inArr1[indexMatched])[0]]
})
// pushing on unmatchedArr
: unmatchedArr.push(obj)
, acc
}, []);
function getIndexMatch(id) {
for (const [index, obj] of inArr1.entries()) {
for (const key of Object.keys(obj)) {
// spliting only digits of the current key of object
if (key.match(/\d/g).includes(id)) return index; // returning index of inArr1 if is included
}
}
return null;
}
// merging arrays
const result = [...matchedArr, unmatchedArr];
console.log(result);

Categories

Resources