Node js , replacing occurrences in a nested data structure - javascript

Basically I want to replace # occurrences in a string from an object. As you can see it replace occurences in templateName , description , comments and Name but I can't replace sections header and sections questions , how will I improve my loop to apply replaceOccurrences in sections.header and sections questions array of objects? . sections headers are array of objects I also want to include that. Any idea? thank you.
Code
const replaceOccurrences = (originalString) => (typeof originalString === 'string' ? originalString.replace(/#/g, '#') : originalString);
const generateTemplate = async (data) => {
for (const [k, v] of Object.entries(data)) { data[k] = replaceOccurrences(v); }
return template(data);
};
Data
data : {
Name: 'Rajesh',
sections: [
{
questions: [Array]
}
],
templateName: 'TEMPLAT#E',
description: 'Tes#t',
comments: "adasdada'dfgdfgdfg 'gfddf#gdfgdf #num;## ##fsdfds gdfgdfgfd##"
}

James's answer will work if your obj is serializable, otherwise you'll need a recursive function
const removePoundSign = cur => {
if (typeof cur === 'string') {
return cur.replace(/#/g, '#');
}
if (cur == null || typeof cur !== 'object') return cur;
if (Array.isArray(cur)) {
return cur.map(el => removePoundSign(el));
}
const newObj = {};
for (const key in cur) {
newObj[key] = removePoundSign(cur[key]);
}
return newObj;
};
also, theres no need for the function to be async in your code!

You can stringify the object, make your replacements, then return it to object form:
let a = JSON.stringify(data).replace(/#/g, '#');
let b = JSON.parse(a);
Name: "Rajesh"
comments: "adasdada'dfgdfgdfg 'gfddf#gdfgdf #num;## ##fsdfds gdfgdfgfd##"
description: "Tes#t"
sections: Array(1)
0: {header: "Testing sec#tion", questions: Array(1)}
length: 1
__proto__: Array(0)
templateName: "TECHNICAL TEMPLAT#E"

Related

Search function that iterates through an array and returns the values matched init also in the child object

I'm trying to search an array of objects with objects that are nested in, so for example i have this array:
[
{
website: 'Stackoverflow',
info: {
"extension": "com",
"ssl": true
}
},
{
website: 'Faceoobok',
info: {
"extension": "com",
"ssl": true
}
}
]
So I want to search all fields, and then also search the object inside and return an array with the method filter, also the char cases won't matter, it needs to return the object in the array even for example Stackoverflow is not the same as stackoverflow with the casing methods that come with JS.
Here is what I've tried, and It searches the objects and returns them but It doesn't search the object inside, what I mean is for example it searchs the website, but not the .info:
const searchMachine = (arr, query) => {
let queryFormatted = query.toLowerCase();
return arr.filter((obj) =>
Object.keys(obj).some((key) => {
if (typeof obj[key] === 'string') {
return obj[key]
.toLowerCase()
.includes(queryFormatted);
}
return false;
})
);
You could take a closure over the wanted string and use a recursive approach for objects.
const
searchMachine = (array, query) => {
const
check = (query => object => Object
.values(object)
.some(value =>
typeof value === 'string' && value.toLowerCase().includes(query) ||
value && typeof value === 'object' && check(value)
))(query.toLowerCase());
return array.filter(check);
},
data = [{ website: 'Stackoverflow', info: { extension: 'com', ssl: true } }, { website: 'Faceoobok', info: { extension: 'com', ssl: true } }];
console.log(searchMachine(data, 'stack'));
console.log(searchMachine(data, 'com'));
You can split the task in two step. The first one is to get all string in the object.
function getAllStrings(obj) {
if(typeof obj === 'object'){
return Object.values(obj).flatMap(v => getAllStrings(v));
}else if (typeof obj === 'string'){
return [obj];
}
return [];
}
And the second one is to filter.
const searchMachine = (arr, query) => {
const queryFormatted= query.toLowerCase();
return getAllStrings(arr).filter(s => s.toLowerCase().includes(queryFormatted));
}
You can reuse the Object.keys.some(...) code you used to search in the object, to search in object.info.
First make a function of it that lets us pass in the object:
const findInObject = (obj) =>
Object.keys(obj).some((key) => {
if (typeof obj[key] === 'string') {
return obj[key]
.toLowerCase()
.includes(queryFormatted);
}
return false;
});
Then call it within arr.filter. findInObject(obj) is your original logic, and check for the presence of obj.info and then call findInObject on obj.info
...
return arr.filter((obj) =>
findInObject(obj) || obj.info && findInObject(obj.info)
);
...

Access nth level nested attribute in javascript object using dynamic variable

Suppose I have an object
const aggs = {
cat.id: {
terms: { field: 'cat.id', size: 10 },
aggs: { cat.label: {field: 'cat.label', size: 5} }
}
}
In the dynamic variable I have
const key = '[cat.id].aggs'
Now I need to access the attribute aggs inside aggs object using dynamic variable
aggs[key]
which is not working, How can I access the nested attributes dynamically? Obviously accessing it directly like this works
aggs['cat.id'].aggs
You can use the usual solutions, but with a specific regular expression that deals appropriately with the brackets, allowing dots in the property names:
const pathToArray = path =>
Array.from(path.matchAll(/\[(.*?)\]|[^.[]+/g) || [],
([prop, inbrackets]) => inbrackets || prop);
function deepGet(obj, path, defaultVal = null) {
for (let prop of pathToArray(path)) {
if (!(prop in obj)) return defaultVal;
obj = obj[prop];
}
return obj;
}
function deepSet(obj, path, value) {
let arr = pathToArray(path);
arr.slice(0, -1).reduce((a, c, i) =>
Object(a[c]) === a[c] ? a[c] :
a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {},
obj)[arr[arr.length - 1]] = value;
return obj;
}
// Your example:
const aggs = {"cat.id": {terms: {field: "cat.id",size: 10},aggs: {"cat.label": {field: "cat.label",size: 5}}}};
const key = "[cat.id].aggs";
console.log(deepGet(aggs, key));
deepSet(aggs, key, "hello");
console.log(deepGet(aggs, key));

How can i get the keys of an object stored in Array

Lets just prefix this for the moderators this question is not about nested Key's. This is about an array in an object and how to see if its a simple array with only values or if it is an array which holds objects and how to get the keys of these objects. I included code snipped which parses the sample and detects the 2 Array's. What i am looking for is to return only name of array if the array is simple array with only a list of values. If the array is array of objects i would like to get the keys of that object in array.
obj = {
DocId: "email_campaign::3ed76589-4063-49f6-a21e-9ca16981d102",
history: {
created_by: "",
created_on: "",
update_on: "",
updated_by: ""
},
librarys :[{id: 1, name : 'Lib 1'},{ id: 2, name: 'Lib 2'}],
status: "Active",
subject: "Test 1 Subject",
summary: "",
tags: ['one', 'two'],
template_id: ""
};
const keyify = (obj, prefix = '') =>
Object.keys(obj).reduce((res, el) => {
if( Array.isArray(obj[el]) ) {
// Here needs to go the Array Part
console.log(el + ' is Array')
return [...res, el];
} else if( typeof obj[el] === 'object' && obj[el] !== null ) {
return [...res, ...keyify(obj[el], prefix + el + '.')];
} else {
return [...res, el];
}
}, []);
const output = keyify(obj);
console.log(output);
Assuming you want the names of arrays prefixed similarly to the existing prefix for objects, you could try this:
const keyify = (obj, prefix = "") =>
Object.keys(obj).reduce((res, el) => {
const elDisplayName = `${prefix}${el}`;
if (Array.isArray(obj[el])) {
const objectsInArray = obj[el].filter(el => typeof el === "object");
if (objectsInArray.length > 0) {
let objectKeys = [];
objectsInArray.map(object => {
objectKeys = objectKeys.concat(keyify(object, prefix + el + "."))
});
return [...res, ...new Set(objectKeys)];
}
return [...res, elDisplayName];
} else if (typeof obj[el] === "object" && obj[el] !== null) {
return [...res, ...keyify(obj[el], prefix + el + ".")];
} else {
return [...res, elDisplayName];
}
}, []);
There are many caveats to this solution, like assuming if an array has a single object, it will contain only objects. But this should give you a start on detecting the presence of objects in arrays.
You can use .every() on an array to check every item against a case.
To check for an array you can use Array.isArray().
To check for an object you can use typeof variable === 'object' however, arrays are also classed as object types so you will need to check it is also not an array with the above method.
Example using your use case
const isObject = (arrayItem) => {
if (typeof arrayItem === 'object' && !Array.isArray(arrayItem) && arrayItem !== null) {
return true;
}
return false;
}
const array1 = [
[1, 2],
[1, 2],
[1, 2]
];
const array2 = [{
key: 'value'
}, {
key2: 'value'
}];
console.log('array1', array1.every(isObject)); // false
console.log('array2', array2.every(isObject)); // true

Add params to function to only execute if value matches name in array

I have an object:
const oData = {
name: 'Hound',
weapon: 'sword',
likes: 'Chicken, Arya, Revenge',
dislikes: 'Fire, Mountain, Lannisters'
};
which I pass to this function:
fConvertValuesToArrays(obj) {
for (const i in obj) {
obj[i] = Array.isArray(obj[i]) ? obj[i] : [obj[i]];
}
return obj;
},
This works as expected in converting all the values into arrays but I now need this to only execute if the value matches with any of the values in this array:
const aNamesToMatch = [ 'likes', 'dislikes' ];
Is it possible to work this into the function or do I need a separate function for this and call that function inside fConvertValuesToArrays?
If so how would that work?
I tried to add an if statement before the ternary but I did not work as expected:
fConvertValuesToArrays(obj) {
for (const i in obj) {
if ( obj.likes || obj.dislikes ) {
obj[i] = Array.isArray(obj[i]) ? obj[i] : [obj[i]];
}
}
return obj;
},
You can use includes() on the array aNamesToMatch. And check if current key is inside that array.
const oData = {
name: 'Hound',
weapon: 'sword',
likes: 'Chicken, Arya, Revenge',
dislikes: 'Fire, Mountain, Lannisters'
};
const aNamesToMatch = [ 'likes', 'dislikes' ];
function fConvertValuesToArrays(obj,keys) {
for (const i in obj) {
if (keys.includes(i)) {
obj[i] = Array.isArray(obj[i]) ? obj[i] : [obj[i]];
}
}
return obj;
}
console.log(fConvertValuesToArrays({...oData},aNamesToMatch))
Instead of looping through the entire object, you can just loop through aNamesToMatch. Update each of those properties in the object to an array:
const aNamesToMatch=['likes','dislikes'],
oData={name:'Hound',weapon:'sword',likes:'Chicken, Arya, Revenge',dislikes:'Fire, Mountain, Lannisters'};
function fConvertValuesToArrays(obj, keys) {
for (const key of keys) {
if (!Array.isArray(obj[key]))
obj[key] = [obj[key]]
}
return obj;
}
console.log(fConvertValuesToArrays(oData, aNamesToMatch))
If there is a possibility of having a key in the array which doesn't exist in the object, you can check if the key exists in the object first:
if (key in obj && !Array.isArray(obj[key])) {
}
Make this change
if(i == 'likes' || i == 'dislikes')
{
obj[i] = Array.isArray(obj[i]) ? obj[i] : [obj[i]];
}
This will check if the key is likes/dislikes and creates an array only if that is the case.

lodash orderby with null and real values not ordering correctly

I have an Angular 2 typescript application that is using lodash for various things.
I have an array of objects that I am ordering using a property in the object...
_.orderBy(this.myArray, ['propertyName'], ['desc']);
This works well however my problem is that sometimes 'propertyName' can have a null value.
These are ordered as the first item in a descending list, the highest real values then follow.
I want to make these null values appear last in the descending ordering.
I understand why the nulls come first.
Does anyone know how to approach this?
The _.orderBy() function's iteratees can use a method instead of a string. Check the value, and if it's null return an empty string.
const myArray = [{ propertyName: 'cats' }, { propertyName: null }, { propertyName: 'dogs' }, { propertyName: 'rats' }, { propertyName: null }];
const result = _.orderBy(myArray, ({ propertyName }) => propertyName || '', ['desc']);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
The check can be simple (like the one I've used), which converts all falsy values to an empty string:
propertyName || ''
If you need a stricter check, you can use the ternary operator, and handle just null values:
propertyName === null ? '' : propertyName
Edit: Example with multiple ordering:
const result = _.orderBy(myArray, (item) => [get(item, 'propertyName', 0), get(item, 'propertyName2')], ['desc', 'asc']);
This will order by propertyName then propertyName2.
If propertyName is undefined/null then its default order will be set to 0. (and therefore will be displayed at last because of desc ordering on the propertyName field). In such case, propertyName2 will therefore determine the ordering.
The code I needed looks like this...
_.orderBy(this.myArray, [( o ) => { return o.myProperty || ''}], ['desc']);
Just for future reference to others you can do this to sort ascending with falsey values at the end.
items =>
orderBy(
items,
[
i => !!i.attributeToCheck,
i => {
return i.attributeToCheck ? i.attributeToCheck.toLowerCase() : ''
}
],
['desc', 'asc']
)
mine looks like this. PropName and sort are both variables in my solution
return _.orderBy( myarray, [
( data ) => {
if ( data[propName] === null ) {
data[propName] = "";
}
return data[propName].toLowerCase();
}
], [sort] );
I wanted tolowercase because otherwise the sorting is not correct if different casings
This will put bad values at the bottom, and it differentiates between numbers and strings.
const items = [] // some list
const goodValues = isAscending => ({ value }) => {
if (typeof value !== 'string' && isNaN(value)) {
return isAscending ? Infinity : -Infinity
}
return value || ''
}
const sortedItems = orderBy(
items,
[goodValues(isAscending), 'value'],
[isAscending ? 'asc' : 'desc']
)
This worked for me
orders = [{id : "1", name : "test"}, {id : "1"}];
sortBy = ["id", "name"];
orderby(
orders,
sortBy.map(s => {
return (r: any) => {
return r[s] ? r[s] : "";
};
})),
);
I created a function for this (ts code):
const orderByFix = (array: any[], orderKeys: string[], orderDirs: ('asc' | 'desc')[]) => {
const ordered = orderBy(array, orderKeys, orderDirs);
const withProp = ordered.filter((o) => orderKeys.every(k => o[k]));
const withoutProp = ordered.filter((o) => !orderKeys.every(k => o[k]));
return [...withProp, ...withoutProp];
};
I've extended gwendall's answer to also handle case when "order keys" are functions (_.orderBy allows that)
const orderByFix = (
array: any[],
orderKeys: (string | ((o: any) => any))[],
orderDirs: ('asc' | 'desc')[]
) => {
const ordered = orderBy(array, orderKeys, orderDirs)
const withProp = ordered.filter((o) =>
orderKeys.every((k) => {
if (typeof k === 'string') {
return o[k]
} else if (typeof k === 'function') {
return k(o)
} else {
throw Error(`Order key must be string or function not ${typeof k}`)
}
})
)
const withoutProp = ordered.filter(
(o) =>
!orderKeys.every((k) => {
if (typeof k === 'string') {
return o[k]
} else if (typeof k === 'function') {
return k(o)
} else {
throw Error(`Order key must be string or function not ${typeof k}`)
}
})
)
return [...withProp, ...withoutProp]
}

Categories

Resources