Remove objects from a deeply nested object array by property value - javascript

Consider I have a nested object array. One possible example scenario could be:
content: [
{
prop1: someValue,
prop2: someValue,
content: [
{
prop2: someValue,
prop3: someValue,
myProperty: myValue
},
{
prop1: someValue,
prop3: someValue,
myProperty: otherValue
}
]
},
{
prop5: someValue,
prop2: someValue
}
]
Here are the possibilities:
The structure starts with content[] but the descendants may or may not have content property.
The level of the hierarchy can be of any number.
The properties contained by the objects are not always the same i.e. one object may have x, y, z properties while the other may have v, w, z properties.
If any object in the hierarchy has myProperty key, there won't be content key.
More than one object in the hierarchy can have myProperty with value'myValue.
My requirement:
If at any level an object has the property myProperty with the value myValue then remove the entire object (NOT JUST THE PROPERTY) from the hierarchy.
My attempt so far:
private removeObjects(content: any, values: string[]): any {
if (!content || content.length === 0) {
return
}
content = content.filter((c) => {
if (!c.myProperty) return true
return c.myProperty.indexOf(values) > 0
})
// Here is my problem since I am supposed to do a recursive call on each of child contents,
// how do I merge back the original array?
return this.removeObjects(content, values)
}

The following recursively returns a new array without mutating the original
const content = [{
prop1: "someValue",
prop2: "someValue",
content: [{
prop2: "someValue",
prop3: "someValue",
myProperty: "myValue"
},
{
prop1: "someValue",
prop3: "someValue",
myProperty: "otherValue"
}
]
},
{
prop5: "someValue",
prop2: "someValue"
}
]
function removeObjects(content) {
return content.reduce((arr, obj) => {
if (obj["myProperty"] && obj["myProperty"] === "myValue") {
return arr
} else if (obj["content"] && obj["content"].length) {
arr.push({ ...obj,
content: removeObjects(obj["content"])
})
return arr
} else {
arr.push(obj);
return arr;
}
}, []);
}
console.log(removeObjects(content))
Expected output:
const content = [{
prop1: "someValue",
prop2: "someValue",
content: [
{
prop1: "someValue",
prop3: "someValue",
myProperty: "otherValue"
}
]
},
{
prop5: "someValue",
prop2: "someValue"
}
]

You can use below function to get expected result:
let data = {
content: [
{
prop1: 'someValue',
prop2: 'someValue',
content: [
{
prop2: 'someValue',
prop3: 'someValue',
myProperty: 'myValue'
},
{
prop1: 'someValue',
prop3: 'someValue',
myProperty: 'otherValue'
}
]
},
{
prop5: 'someValue',
prop2: 'someValue'
}
]
}
function removeMyvalyeObj(data) {
for (let i = data.content.length - 1; i >= 0; i--) {
if (data.content[i].myProperty === 'myValue') {
data.content.splice(i, 1);
} else if(data.content[i].content) {
removeMyvalyeObj(data.content[i])
}
}
}
removeMyvalyeObj(data);
console.log(data);

Try this, (this is JavaScript version)
let someValue = 'a';
let otherValue ='x';
let myValue = 'xy';
let content = [
{
prop1: someValue,
prop2: someValue,
content: [
{
prop2: someValue,
prop3: someValue,
myProperty: myValue
},
{
prop1: someValue,
prop3: someValue,
myProperty: otherValue
}
]
},
{
prop5: someValue,
prop2: someValue
}
];
function removeObjects(content, values) {
debugger;
if (!content || content.length === 0) {
return
}
content = content.filter((c) => {
if (!c.myProperty) return true
return c.myProperty.indexOf(values) > 0
})
// Here is my problem since I am supposed to do a recursive call on each of child contents,
// how do I merge back the original array?
return content.map(x => ({
...x,
content: removeObjects(x.content, values)
}))
}
console.log(removeObjects(content, 'x'))

Use recursive approach, to have filter array of items and their content array.
const filter = (arr, name, value) =>
arr
.filter((obj) => !(name in obj && obj[name] === value))
.map((obj) => {
const nObj = { ...obj };
if (obj.content && Array.isArray(obj.content)) {
nObj.content = filter(obj.content, name, value);
}
return nObj;
});
content = [
{
prop1: 'someValue',
prop2: 'someValue',
content: [
{
prop2: 'someValue',
prop3: 'someValue',
myProperty: 'myValue',
},
{
prop1: 'someValue',
prop3: 'someValue',
myProperty: 'otherValue',
},
],
},
{
prop5: 'someValue',
prop2: 'someValue',
},
];
const updated = filter(content, "myProperty", "myValue");
console.log(updated);

Related

Create an array based on two objects

I have two objects, that contains same number of properties:
object1: {
prop1: 'data12',
prop2: 'data13',
prop3: 'data58',
prop4: 'xyz',
// more props here...
}
object2: {
prop1: 123,
prop2: 'email#gmail.com',
prop3: 'text',
prop4: 'bla',
// more props here...
}
I want to build an array {name: '', value: ''} based on this objects:
[
{ data12: 123 },
{ data13: 'email#gmail.com' },
{ data58: text },
{ xyz: 'bla' },
]
How can you do that? The number of properties could be different.
Just using Object.keys() can do it
let object1 = {
prop1: 'data12',
prop2: 'data13',
prop3: 'data58',
prop4: 'xyz',
}
let object2 = {
prop1: '123',
prop2: 'email#gmail.com',
prop3: 'text',
prop4: 'bla',
}
let result =[]
let obj = {}
Object.keys(object1).forEach(k =>{
obj[object1[k]] = object2[k]
})
result.push(obj)
// combined with reduce can also do it
/*
let obj =Object.keys(object1).reduce((a,c) => {
a[object1[c]] = object2[c]
return a
},{})
*/
result.push(obj)
console.log(result)

Filter array by object property values

I am programming a function that will handle javascript array filtering. I know the values by which I want to filter so I know how to do it in a fairly easy way, but I would like the code to be more extensible.
I wrote such a function:
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = { prop2: 'Paris', prop3: '987' };
const handleFilters = (items, filters) => {
return items.filter((item) => {
if (filters.prop3 && filters.prop2) {
return item.prop3 === filters.prop3 && item.prop2 === filters.prop2;
}
if (filters.prop3) {
return item.prop3 === filters.prop3;
}
if (filters.prop2) {
return item.prop2 === filters.prop2;
}
});
}
I am not completely satisfied with it. I think it could be written better. If the 3rd argument comes, I don't want to add it to the if - it should be automatic.
I've searched several topics on stackoverflow, looked through the lodash documentation looking for some good solution but I have no idea what I can do better with this.
If I understood correctly, you want to only keep the items that match all the filter:
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = {prop2: 'Paris', prop3: '987'};
const handleFilters = (items, filters) => {
return items.filter((item) =>
Object.entries(filters).every(([key, val]) => item[key] === val)
)
};
console.log(handleFilters(items, filters))
This basically checks that every (key, val) of the filter exists in the item
private handleFilters (items, ...props) {
return items.filter(item => props.every(prop => item[prop] === prop));
}
To use the filter object dynamically, you could look into using something like Object.keys().
Here's your example code changed to use that:
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = { prop2: 'Paris', prop3: '987' };
const handleFilters = (items, filters) => {
return items.filter(item => {
return Object.keys(filters).some(filterKey => item[filterKey] === filters[filterKey])
});
}
It loops through the items, the same as you already were doing. However, now it also loops through the keys set in the filters object. The some() function returns a boolean; true if the callback is true and false if the callback is false. Here it checks if the value in item[filterKey] is the same as the value in filters[filterKey].
It's important to note that this returns the item if any of the filter values matches with an item's property. If all values must be in the filtered item, you'll want to use Object.keys(filters).every(filterKey => item[filterKey] === filters[filterKey]).
The every function returns true only if all filterKeys have the same value as the keys checked in item.
It is also possible to use Object.entries(), which allows directly using the value, but for this example I chose to use just the keys, for consistency.
You can filter by using Object.key , filter and every method in precise manner.
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = { prop2: 'Paris', prop3: '987' };
const handleFilter = (items, filters) => {
return items.filter((item) => Object.keys(filters).every((key) => item[key] === filters[key]));
}
console.log(handleFilter(items, filters))
You can remove your multiple if blocks by using for..in
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = { prop2: 'Paris', prop3: '987' };
const handleFilters = (items, filters) => {
return items.filter((item) => {
for(const key in filters) {
if (filters[key] !== item[key]) {
return false;
}
}
return true;
});
}
console.log(handleFilters(items, filters))
It does the same as your code was doing.
Some Improvement over #David Alvarez approach, here i have used Object.keys instead of Object.entries as Object.key is faster than Object.entries here is the comparison: https://www.measurethat.net/Benchmarks/Show/3685/0/objectentries-vs-objectkeys-vs-objectkeys-with-extra-ar
const items = [
{
prop1: 'Jotahan',
prop2: 'London',
prop3: '1234567'
},
{
prop1: 'Jones',
prop2: 'Paris',
prop3: '987'
}
];
const filters = {prop2: 'Paris', prop3: '987'};
const handleFilters = (items, filters) => (
items.filter((item) =>
Object.keys(filters).every(key => item[key] === filters[key])
)
);
console.log(handleFilters(items, filters))

Angular 11 can't access object property dynamically

I have to access to a subset of my object properties and store the label and the value in another array. Basically I have this object:
myObject: CustomObject {
prop1: value1,
prop2: value2
prop3: value3
}
and as output I need an array with objects of type: {label: string, value: number}.
I need to fill this array with prop1 and prop2 and their values, so that I have something like this:
myArray = [{label: prop1, value: value1}, {label: prop2, value: value2}]
What I've tried is this:
labels = ['prop1', 'prop2'];
labels.forEach((l: any) => {
this.myArray.push({ label: l, value: this.myObject[l] })
})
or also this.myObject.l
But I get this error:
Element implicitly has an 'any' type because expression of type 'any'
can't be used to index type 'MyObject'
I've changed the type of "l" to string but I got:
No index signature with a parameter of type 'string' was found on type
What's the correct way to do this?
myObject
Instead of any, you can use keyof to tell typescript that labels is an array of strings where strings are the keys of your interface:
Playground Link
interface CustomObject {
prop1: any;
prop2: any;
prop3: any;
}
const myObject: CustomObject = {
prop1: 1,
prop2: 2,
prop3: 3,
}
const labels: (keyof CustomObject)[] = ['prop1', 'prop2'];
const myArray = labels.map(label => ({ label, value: myObject[label] }))
console.log(myArray)
You can use Object.entries function to iterate over key/value pairs:
const myObject = { prop1: 'value1', prop2: 'value2', prop3: 'value3' }
let myArray = [];
Object.entries(myObject).map((item)=>{ myArray.push({label:item[0], value:item[1]})})
console.log(myArray)

How to access the keys of an indexed array

Say I have the array below, and I want to retrieve the keys inside the indexed object, so that I would end up with an array like this ["prop1", "prop2", "prop3", "prop3"]
const testArray =  [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
]
Using reduce and concat:
const testArray = [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
]
const keys = testArray.reduce((arr, curr, index) =>
arr.concat(Object.keys(curr[index])),[])
console.log(keys)
// ["prop1", "prop2", "prop3", "prop4"]
You can map over the array, taking the value's keys
const testArray = [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
]
const keys = testArray
.map(e => Object.keys(...Object.values(e)))
.flat();
console.log(keys);
Note that flat() is experimental for now, so you might want to use reduce or so to flatten the array...
#bambam has given the solution with "map", which is clearer.
You can also do that with "reduce" as below.
testArray.reduce((arr, d) =>
(arr.push(
Object.entries(d).map(([, l]) => Object.keys(l))
)
, arr)
,[]).flat(2)
One solution with forEach
const testArray = [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
];
var arr = [];
testArray.forEach((data, index) => {
let tempArr = Object.keys(data[index]);
arr = arr.concat(tempArr);
})
console.log(arr);
const testArray = [
{
0: {
prop1: "sdsd",
prop2: "ssdsd"
}
},
{
1: {
prop3: "sdsd",
prop4: "sddd"
}
}
];
var arr = testArray.map((p)=>{return Object.keys(p[Object.keys(p)[0]]);});
console.log([].concat.apply([],arr));

Renaming or setting object names dynamically in JavaScript

I'm fairly new to JavaScript I setup below to use keys and values for different items I need to work. All the values will be the same using a variable to distinguish each item (which will just be it, but I would like to identify each item (itemObject) which I'm also using for the variable name.
Abbreviated example::
function objFunction (itemName) {
itemObject = {
object1Data: [{
data1: {
prop1: 'value',
prop2: 'value'
},
data2: {
prop1: itemName + 'some string'
prop2: itemNamevariable
}
}],
object2: {
data: {
prop1: itemName,
prop2: itemName + 'some string'
}
}
}
}
Or am I better off using a constructor or another method?
function ObjFunction (itemName) {
this.itemObject = {
bject1Data: [{
data1: {
prop1: 'value',
prop2: 'value'
},
data2: {
prop1: itemName + 'some string',
prop2: itemName
}
}],
object2: {
data: {
prop1: itemName,
prop2: itemName + 'some string'
}
}
}
}
var item1 = new ObjFunction('item1Name');
I prefer the first answer, just change the second line from:
itemObject = {
to
return {
and then you can say:
var item1 = objFunction('item1Name');
In other words:
function objFunction (itemName) {
return {'name' : itemName};
}
var y = objFunction("theName");
console.log(y.name);

Categories

Resources