updating nested objects in for loop - javascript

I have the following issue I have a set of JSON rules like so
{
"event": {
"type": "maxrulecount",
"params": {
"maxrulecount": 2
}
},
"conditions": {
"any": [
{
"all": [
{
"fact": "apples",
"value": "20",
"operator": "greaterThanInclusive"
}
]
},
{
"all": [
{
"fact": "bananas",
"value": "100",
"operator": "greaterThanInclusive"
}
]
}
]
}
}
So i obviously convert this to an object but the number value remains a string so I have created a function that will convert any numbers that are strings into numbers like so
checkForNumberValues(rules) {
// allows any number of numbers together or a decimal number
let numberRegex = /^(([0-9]{1,})|([0-9]{1,}\.[0-9]{1,}))$/g;
// yes a loop within a loop but time complexity is no issue here
rules?.conditions?.any?.forEach((condition) => {
condition?.all?.forEach((rule) => {
console.log(rule.value, numberRegex.test(rule.value)); // this is working correctly
if (numberRegex.test(rule.value)) {
rule.value = Number(rule.value);
}
});
});
console.log(rules);
return rules;
}
now i can see that it is correctly identifying numbers and setting the value but when i console the result like so
console.log(checkForNumberValues(rules));
I'ts returning the rules object with the string number values instead of the number values I set..
Do I need to do something special to set nested values??
Below is an example
let rules = {
conditions: {
any: [
{
all: [
{ fact: 'orange', value: '70' },
{ fact: 'apple', value: '10' },
{ fact: 'brocolli', value: '54' },
{ fact: 'kiwi fruit', value: '199' }
]
}
]
}
}
function checkForNumberValues(rules) {
let numberRegex = /^(([0-9]{1,})|([0-9]{1,}\.[0-9]{1,}))$/g;
rules.conditions.any.forEach((condition) => {
condition.all.forEach((rule) => {
if (numberRegex.test(rule.value)) {
rule.value = Number(rule.value);
}
})
});
return rules;
}
console.log(checkForNumberValues(rules));
Any help would be appreciated!

Regexp "remembers" the last index where a match was found when the global flag g is used (-> Why does a RegExp with global flag give wrong results?)
Use parseInt()/Number() and then test for NaN
let rules = {
conditions: {
any: [
{
all: [
{ fact: 'orange', value: '70' },
{ fact: 'apple', value: '10' }
]
}
]
}
}
function checkForNumberValues(rules) {
rules.conditions.any.forEach((condition) => {
condition.all.forEach((rule) => {
const val = parseInt(rule.value);
if (!isNaN(val)) {
rule.value = val;
}
})
});
return rules;
}
console.log(checkForNumberValues(rules));

Use isNaN to check if the string is a number or not.
let rules = {
conditions: {
any: [
{
all: [
{ fact: 'orange', value: '70' },
{ fact: 'apple', value: '10' },
{ fact: 'brocolli', value: '54' },
{ fact: 'kiwi fruit', value: '199' }
]
}
]
}
}
function checkForNumberValues(rules) {
rules.conditions.any.forEach((condition) => {
condition.all.forEach((rule) => {
if (!isNaN(rule.value)) {
rule.value = Number(rule.value);
}
})
});
return rules;
}
console.log(checkForNumberValues(rules));

you can try a different approach
let rules = {
conditions: {
any: [
{
all: [
{ fact: 'orange', value: '70' },
{ fact: 'apple', value: '10' }
]
}
]
}
}
rules.conditions.any.filter(x=>{(x.all).forEach(x=>x.value=parseInt(x.value))})
console.log(rules)

Related

Filter an array of object based on key name of the object

I have an array of objects that looks like this:
var data = [
{
abc: { name:"abc" }
},
{
new_abc: {name:"hello" }
},
{
def: { name:"def" }
},
{
ghi: { name:"ghi" }
},
{
new_ghi: { name:"world" }
},
{
new_jkl: { name:"javascript" }
},
{
lmn: { name:"lmn" }
},
];
I want only the objects that have a key whose name starts with "new". In the above array, I have 3 such objects.
I want the output to be this:
[
{
new_abc:{name:"hello"}
},
{
new_ghi:{name:"world"}
},
{
new_jkl:{name:"javascript"}
},
]
You can do the following,
var data = [
{
abc:{name:"abc"}
},
{
new_abc:{name:"hello"}
},
{
def:{name:"def"}
},
{
ghi:{name:"ghi"}
},
{
new_ghi:{name:"world"}
},
{
new_jkl:{name:"javascript"}
},
{
lmn:{name:"lmn"}
}
]
res = data.filter(item => Object.keys(item).some(key => key.indexOf('new') === 0));
console.log(res);
I want only the objects that have a key whose name starts with "new"
You can use Array#filter combined with String.prototype.startsWith() like this way.
const data = [{abc:{name:"abc"}},{new_abc:{name:"hello"}},{def:{name:"def"}},{ghi:{name:"ghi"}},{new_ghi:{name:"world"}},{new_jkl:{name:"javascript"}},{lmn:{name:"lmn"}}];
console.log(data.filter(item => Object.keys(item)[0].startsWith('new')));
The startsWith() method determines whether a string begins with the
characters of a specified string, returning true or false as
appropriate.

Transforming an array into an array of arrays

I am trying to do an opposite of flattening an array.
I have the following input JSON array of 4 elements:
[
{
"nestedObj": {
"id":12
}
},
{
"nestedObj": {
"id":555
}
},
{
"nestedObj": {
"id":555
}
},
{
"nestedObj" :{
"id":771
}
}
]
I want to transform it to an array of arrays, where each subarray has elements of the same nestedObj.id grouped up together.
I can assume the initial JSON is sorted by nestedObj.id.
In the above example, the id of nestedObj of 2nd and 3rd element are the same (555), so those elements would be grouped into one sub-array.
This would be the result, an array of only 3 sub-array elements:
[
[{
"nestedObj": {
"id":12
}
}],
[{
"nestedObj": {
"id":555
}
},
{
"nestedObj": {
"id":555
}
}],
[{
"nestedObj" :{
"id":771
}
}]
]
And this is the code that gets me what I want:
const data = [ /* ...the above input data... */ ];
let result = [];
let prevId = null;
for (let elem of data) {
let currId = elem.nestedObj.id;
if (currId === prevId) {
result[result.length - 1].push({...elem});
} else {
result.push([{...elem}]);
}
prevId = currId;
}
But as you can see... the code is very declarative. It's not very JavaScript-like, in a functional programming sense.
How can I re-write it using e.g. reduce or other 'modern JS' techniques?
Just group the objects.
let array = [{ nestedObj: { id: 12 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 771 } }],
result = Object.values(array.reduce((r, o) => {
(r[o.nestedObj.id] = r[o.nestedObj.id] || []).push(o);
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can group by id using the function reduce, extract the grouped values using the function Object.values, and finally map the array to build the desired output.
This is assuming we have only one attribute called nestedObj
let arr = [{ nestedObj: { id: 12 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 771 } }],
result = Object.values(arr.reduce((a, {nestedObj: {id}}) => {
(a[id] || (a[id] = [])).push(id);
return a;
}, {})).map(r => r.map(id => ({nestedObj: {id}})));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use a Map to group the items with same id then get the final values from the Map
const data = [{ nestedObj: { id: 12 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 771 } }]
const map = new Map;
data.forEach(o => {
const {nestedObj:{id}} = o;
map.has(id) ? map.get(id).push(o) : map.set(id,[o]);
});
console.log([...map.values()])

Recursive function to format nested arrays to match against arrays of conditions

Trying to write this eval function that takes in cases, loops through them and needs to check if it matches to a condition in a conditions array and returns true or false. I'm not sure how best to format the conditions array and run the matching on it. Conditions are nested, n-levels deep, so trying to get a recursive function.
console.log(cases.forEach(c => eval(formattedCondition, c.item)))
const conditions = [
"OR",
["AND",["==","maker","airbus"],["==","name","A320"]],
["AND",[ "==", "maker","boeing"]],
["OR",["==","name","B767"]]
]
const cases = [
{
"item": {
'maker': 'airbus',
'name':"A320",
}
// should return true for this case
},
{
"item": {
'maker': 'embraer',
'name':"e175",
}
// should return false for this case
},
{
"item": {
'maker': 'boeing',
}
// should return true for this case
},
{
"item": {
'name':"B767",
}
// should return true for this case
},
{
"item": {
'maker': 'boeing',
'name':"B777",
}
// should return false for this case
},
]
You could take an approach without eval and use the data to build expressions with a function for checking with equal and some quantizers for AND and OR.
const
conditions = ["OR", ["AND", ["==", "maker", "airbus"], ["==", "name", "A320"]], ["AND", ["==", "maker", "boeing"]], ["OR", ["==", "name", "B767"]]],
take = object => {
const
quantifiers = { AND: 'every', OR: 'some' },
operators = { '==': (a, b) => a == b },
evaluate = ([symbol, ...values]) => values.every(v => typeof v === 'string')
? operators[symbol](object[values[0]], values[1])
: values[quantifiers[symbol]](evaluate);
return evaluate;
},
cases = [
{ item: { maker: 'airbus', name: "A320" } }, // true
{ item: { maker: 'embraer', name: "e175" } }, // false
{ item: { maker: 'boeing' } }, // true
{ item: { name: "B767" } }, // true
{ item: { maker: 'boeing', name: "B777" } }, // true instead of false
],
result = cases.map(({ item }) => take(item)(conditions));
console.log(result);

Deep replace a value knowing the value of an attribute within the object we must inject value in

We have a deeply nested structure which varies each time we run the app.
{
some: {
complex: {
unknown: {
structure: {
fields: [
{ name: "group1", other: "data", currentValue: "" },
{ name: "group2", other: "another data", currentValue: "" },
]
}
}
}
}
}
We must inject, in this structure, proper value. We receive for example
{
group1: 'the proper value'
}
And we must replace the value in the proper group to obtain:
{
some: {
complex: {
unknown: {
structure: {
fields: [
{ name: "group1", other: "data", currentValue: "the proper value" },
{ name: "group2", other: "another data", currentValue: "" },
]
}
}
}
}
}
We tried to use lodash mergeWith but since we cannot know where exactly is the value we must inject and we only know the value of of of the key of the object we must inject the value in, we didn't manage to get this working.
Have a recursive function going through the object and mutating it depending on the value of what you seek.
const obj = {
some: {
complex: {
unknown: {
structure: {
fields: [{
name: 'group1',
other: 'data',
currentValue: '',
},
{
name: 'group2',
other: 'another data',
currentValue: '',
},
],
},
},
},
},
};
const toChange = {
group1: 'the proper value',
group2: 'the proper value 2',
};
// Recursive function that go replace
function lookAndReplace(config, ptr) {
// If we deal with an object look at it's keys
if (typeof ptr === 'object') {
Object.keys(ptr).forEach((x) => {
// If the keys is the one we was looking for check the value behind
if (x === config.keyToCheck) {
// We have found one occurence of what we wanted to replace
// replace the value and leave
if (ptr[x] === config.key) {
ptr[config.keyToReplace] = config.value;
}
return;
}
// Go see into the value behind the key for our data
lookAndReplace(config, ptr[x]);
});
}
// If we are dealing with an array, look for the keys
// inside each of the elements
if (ptr instanceof Array) {
ptr.forEach(x => lookAndReplace(config, x));
}
}
// For each group we look for, go and replace
Object.keys(toChange).forEach(x => lookAndReplace({
key: x,
value: toChange[x],
keyToCheck: 'name',
keyToReplace: 'currentValue',
}, obj));
console.log(obj);
/!\ Important this soluce also work with nested arrays
const obj = {
some: {
complex: {
unknown: {
structure: {
// fields is an array of array
fields: [
[{
name: 'group1',
other: 'data',
currentValue: '',
}],
[{
name: 'group2',
other: 'another data',
currentValue: '',
}],
],
},
},
},
},
};
const toChange = {
group1: 'the proper value',
group2: 'the proper value 2',
};
// Recursive function that go replace
function lookAndReplace(config, ptr) {
// If we deal with an object look at it's keys
if (typeof ptr === 'object') {
Object.keys(ptr).forEach((x) => {
// If the keys is the one we was looking for check the value behind
if (x === config.keyToCheck) {
// We have found one occurence of what we wanted to replace
// replace the value and leave
if (ptr[x] === config.key) {
ptr[config.keyToReplace] = config.value;
}
return;
}
// Go see into the value behind the key for our data
lookAndReplace(config, ptr[x]);
});
}
// If we are dealing with an array, look for the keys
// inside each of the elements
if (ptr instanceof Array) {
ptr.forEach(x => lookAndReplace(config, x));
}
}
// For each group we look for, go and replace
Object.keys(toChange).forEach(x => lookAndReplace({
key: x,
value: toChange[x],
keyToCheck: 'name',
keyToReplace: 'currentValue',
}, obj));
console.log(obj);
const obj = {
some: {
complex: {
unknown: {
structure: {
fields: [{
name: "group1",
other: "data",
currentValue: ""
},
{
name: "group2",
other: "another data",
currentValue: ""
},
]
}
}
}
}
};
const toChange = {
group1: 'the proper value',
group2: 'the proper value 2',
};
// Recursive function that go replace
function lookAndReplace({
key,
value,
keyToCheck,
keyToReplace,
}, ptr) {
// If we deal with an object
if (typeof ptr === 'object') {
Object.keys(ptr).forEach((x) => {
if (x === keyToCheck) {
// We have found one
if (ptr[x] === key) {
ptr[keyToReplace] = value;
}
} else {
lookAndReplace({
key,
value,
keyToCheck,
keyToReplace,
}, ptr[x]);
}
});
}
if (ptr instanceof Array) {
ptr.forEach(x => lookAndReplace({
key,
value,
keyToCheck,
keyToReplace,
}, x));
}
}
// For each group we look for, go and replace
Object.keys(toChange).forEach(x => lookAndReplace({
key: x,
value: toChange[x],
keyToCheck: 'name',
keyToReplace: 'currentValue',
}, obj));
console.log(obj);
A solution could be to use a recursive function like this:
object={
some: {
complex: {
unknown: {
structure: {
fields: [
{ name: "group1", other: "data", currentValue: "" },
{ name: "group2", other: "another data", currentValue: "" },
]
}
}
}
}
};
newValue={
group1: 'the proper value'
};
var inserted=false;
function search(data, newData){
if(inserted) return;
for(key in data){
if(data[key]==Object.keys(newData)[0]){
data["currentValue"]=newData[Object.keys(newData)[0]];
inserted=true;
return;
}else
search(data[key], newData);
}
}
search(object, newValue);
console.log(object);
You could do a recursive search and replace...
let theObj = {
some: {
complex: {
unknown: {
structure: {
fields: [
{ name: "group1", other: "data", currentValue: "" },
{ name: "group2", other: "another data", currentValue: "" },
]
}
}
}
}
}
function updateObj(obj, replacement) {
if(Array.isArray(obj)) {
let key = Object.keys(replacement)[0]
let itm = obj.find(i => i.name == key)
itm.data = replacement[key]
} else if(typeof obj == 'object') {
for(let i in obj) {
updateObj(obj[i], replacement)
}
}
}
updateObj(theObj, { group1: 'the proper value' })
console.log(theObj)

Sort array of objects by property length

I have this array:
var itemList = [
{
image: "images/home.jpg",
name: "Home"
},
{
name: "Elvis",
},
{
name: "Jonh"
},
{
image: "images/noah.jpg",
name: "Noah"
},
{
name: "Turtle"
}
]
How can I organize the array to objects with image property come first, so that it looks like this?:
var itemList = [
{
image: "images/home.jpg",
name: "Home"
},
{
image: "images/noah.jpg",
name: "Noah"
},
{
name: "Elvis",
},
{
name: "Jonh"
},
{
name: "Turtle"
}
]
This code put at the beginning elements that have the property 'image'. Other elements stay in the same order.
function compare(a,b) {
if ('image' in a) {
return 1;
} else if ('image' in b) {
return -1;
} else {
return 0;
}
}
itemList.sort(compare);
Try this:
function compare(a,b) {
if (a.image && b.image)
return 0;
if (a.image)
return 1;
return -1;
}
objs.sort(compare);
A bit late, but an alternative:
itemList.sort(function(e1,e2){ return (e1.image === undefined) - (e2.image === undefined); });

Categories

Resources