I have an object that I need to filter against and return a new object. The goal is to get all ids that contain "A" in val, BUT only include ids with a unique val.
Below is what I'm currently doing, but I wonder if there's a more efficient way to do this. As can be seen when you run the code snippet, the new object should look like this:
{
"id1": {
"val": "AAA"
},
"id4": {
"val": "ABC"
}
}
const obj = {
id1: {
val: 'AAA',
},
id2: {
val: 'BBB',
},
id3: {
val: 'AAA',
},
id4: {
val: 'ABC',
},
};
// Filtered object
const obj2 = {};
let matched = '';
for (const key in obj) {
if (matched.indexOf(obj[key].val) < 0 && obj[key].val.indexOf('A') > -1) {
obj2[key] = obj[key];
matched += obj[key].val + ' ';
}
}
console.log(obj2);
Instead of building up a string for matched, you should use a Set (O(1) string comparisons for each operation instead of searching an increasingly long string in time proportional to the length of that string – and not running into issues with keys that contain spaces). includes is also a nice modern alternative to indexOf(…) > -1, although not faster.
Also, when using objects to store key/value mappings, you should use prototypeless objects – starting from Object.create(null) – to avoid setter weirdness (mostly __proto__) and tempting but fragile methods (name collisions with Object.prototype), and as a matter of good practice even when that isn’t a concern. Or just use Maps instead.
const obj = {
id1: {val: 'AAA'},
id2: {val: 'BBB'},
id3: {val: 'AAA'},
id4: {val: 'ABC'},
};
// Filtered object
const obj2 = Object.create(null);
const matched = new Set();
for (const key in obj) {
if (!matched.has(obj[key].val) && obj[key].val.includes('A')) {
obj2[key] = obj[key];
matched.add(obj[key].val);
}
}
console.log(obj2);
Related
So I have a series of objects that are pulled from an API and inputted into an array, something like such:
array = [
{id: 0, name: "First", relationship: "Friend"},
{id: 1, name: "Second", relationship: "Friend"}
]
The user is allowed to add and remove objects to the list freely (they will appear within a Vue.JS DataTable), and said user is allowed a maximum of 4 objects within the array (lets say 4 "friends")
How should I go about implementing a function that searches the existing array (say, if its populated from the API), and inputs the new object with the corresponding ID that is missing (so if the user deletes the object with the id 2, and adds another, it will search said array with objects, find the missing id 2 slot in the array, and input the object in its place)?
Previously I have gone about it via implement array.find() with conditionals to see if the array contains or does not contain the certain id value, however, it searches through each entry and can end up inserting the same object multiple times. Another method I haven't attempted yet would be having a separate map that contains ids, and then when a user removes an object, having it correspond with the map, and vice versa when adding.
Any suggestions? Thanks
Instead of an array, I'd keep an object in data. Have it keyed by id, like this:
let objects = {
0: { id: 0, name: 'name0', relationship: 'relationship0' },
1: { id: 1, name: 'name1', relationship: 'relationship1' },
}
Integer keys in modern JS will preserve insertion order, so you can think of this object as ordered. The API probably returns an array, so do this...
// in the method that fetches from the api
let arrayFromApi = [...];
this.objects = array.reduce((acc, obj) => {
acc[obj.id] = obj; // insertion order will be preserved
return acc;
}, {});
Your UI probably wants an array, so do this (refer to "array" in the markup):
computed: {
array() {
return Object.values(this.objects);
},
To create a new object, insert it in order, minding the available keys. Note this is a linear search, but with small numbers of objects this will be plenty fast
methods: {
// assumes maxId is const like 4 (or 40, but maybe not 400)
createObject(name, relationship) {
let object = { name, relationship };
for (let i=0; i< maxId; i++) {
if (!this.objects[i]) {
object.id = i;
this.objects[i] = object;
break;
}
}
try this,
let array = [
{id: 0, name: "First", relationship: "Friend"},
{id: 4, name: "Second", relationship: "Friend"},
{id: 2, name: "Second", relationship: "Friend"},
]
const addItem = (item) => {
let prevId = -1
// this is unnecessary if your array is already sorted by id.
// in this example array ids are not sorted. e.g. 0, 4, 2
array.sort((a, b) => a.id - b.id)
//
array.forEach(ob => {
if(ob.id === prevId + 1) prevId++
else return;
})
item = {...item, id: prevId + 1 }
array.splice(prevId+1, 0, item)
}
addItem({name: "x", relationship: "y"})
addItem({name: "a", relationship: "b"})
addItem({name: "c", relationship: "d"})
console.log(array)
You can simply achieve this with the help of Array.find() method along with the Array.indexOf() and Array.splice().
Live Demo :
// Input array of objects (coming from API) and suppose user deleted 2nd id object from the array.
const arr = [
{id: 0, name: "First", relationship: "Friend" },
{id: 1, name: "Second", relationship: "Friend" },
{id: 3, name: "Fourth", relationship: "Friend" }
];
// find the objects next to missing object.
const res = arr.find((obj, index) => obj.id !== index);
// find the index where we have to input the new object.
const index = arr.indexOf(res);
// New object user want to insert
const newObj = {
id: index,
name: "Third",
relationship: "Friend"
}
// Insert the new object into an array at the missing position.
arr.splice(index, 0, newObj);
// Output
console.log(arr);
My usage will contain 6 different object types (some which contain double nested arrays), and any possibility of number of entries, on the condition that an given entry is unique.
These objects do not have a consistent unique identifier (a unique identifier is applied in backend on submission).
here is an example of what the array may look like (only 2 object types):
arr = [
{name:"aaa",time:15},
{name:"aaa",time:22},
{timeline: "250", chars[{a},{b},{c}]},
{timeline: "220", chars[{d},{e},{f}]},
]
obj = {name:"aaa",time:22}
My intention is to gain a true or false based on if obj is inside arr
I have tried methods:
I was suggested this method & it errors: #<Object> is not a function
console.log(arr.find(obj))
I also found this suggestion but it will always return false even with the element present
console.log(arr.includes(object))
I tried this method myself, though it will always fail.
console.log(arr.filter((element, index) => element === obj)
With attempt 4, If I was to compare name, this would be insufficient as unique time would be ignored missing valid entries.
If I was to pass every field, this would also not work as each object may or may not have the field and cause error.
Its not really possible to manually pre-filter filter into distinct categories, as every time a new type is added it will need manually adding to the filter.
If there is a library which could do this that you know of, please let me know as that would be perfect. Otherwise any other suggestions (excluding separating arrays) Would be greatly appreciated.
Use arr.some() to check if the required object is present in the array.
To compare the objects, a simpler way is to Stringify both the Objects and compare them.
const arr = [
{name:"aaa",time:15},
{name:"aaa",time:22},
{name: "aaa", chars: ["a", "b", "c"]},
{name: "bbb", chars: ["d", "e", "f"]},
]
const obj1 = {name:"aaa", time: 15}
const obj2 = {name:"aaa",chars: ["a", "b", "c"]}
console.log(arr.some((element) => JSON.stringify(element) === JSON.stringify(obj1))) // true
console.log(arr.some((element) => JSON.stringify(element) === JSON.stringify(obj2))) // true
Didn't give much thought on performance.
I didn't put much thought on performace here but this might help:
function checkObjectInArray(arr, obj) {
const res = arr.some((el) => deepEqual(el, obj));
console.log(res);
}
function deepEqual(obj1, obj2) {
if (Object.keys(obj1).length !== Object.keys(obj2).length) return false;
for (let prop in obj1) {
if (!obj2.hasOwnProperty(prop) || obj2[prop] !== obj1[prop]) {
return false;
}
}
return true;
}
in your case you can use it like:
arr = [
{ name: "aaa", time: 15 },
{ name: "aaa", time: 22 },
{ timeline: "250", data: ["2", "3", "4"] },
{ timeline: "251", data: ["2", "3", "4"] }, // what is chars[{d},{e},{f}] ?!
];
obj = { name: "aaa", time: 22 };
checkObjectInArray(arr, obj);
Observation : arr is not a valid array. Nested chars is not containing a valid value.
Solution : You can simply achieve the requirement by Just converting the JSON object into a JSON string and by comparing.
This solution works fine as you are just trying to find a single object in the passed arr.
Live Demo :
const arr = [
{name:"aaa",time:15},
{name:"aaa",time:22},
{timeline: "250", chars: [{a: 1},{b: 2},{c: 3}]},
{timeline: "220", chars: [{d: 4},{e: 5},{f: 6}]},
];
const obj = {name:"aaa",time:22};
const res = JSON.stringify(arr).indexOf(JSON.stringify(obj)) !== -1 ? true : false;
console.log(res);
Say I have an object:
myObj = {
name: 'Luke',
age: 12,
height: '163cm',
weight: '60kg',
others: { one: '1', two: '2', three: '3'} // (Edited) Added one more key here :)
};
I want a copy of this object without certain keys to a new object in a way that the output is as below:
newObj = {
name: 'Luke',
age: 12,
one: '1',
two: '2'
};
I have seen examples of destructing but I wanted to know if it is possible with nested objects. Is something like this doable using destructuring or if not what would be the most efficient way to do this.
One way to achieve this with destructure-like syntax would be like this:
const myObj = {
name: 'Luke',
age: 12,
height: '163cm',
weight: '60kg',
others: { one: '1', two: '2', three : '3'}
};
const newObj = {
/* Copy over values from "myObj" to equivalent keys in "newObj" */
name : myObj.name,
age : myObj.age,
/* Spread keys "one" and "two" of the nested "others" object into "newObj" */
...({one, two} = myObj.others, {one, two})
}
console.log(newObj)
For completeness, an iife approach is possible. It works by creating an arrow function that takes as parameters keys you want to keep. In the function body, spread nested objects as desired.
const myObj = {
name: 'Luke',
age: 12,
height: '163cm',
weight: '60kg',
others: { one: '1', two: '2'}
};
const newObj = (
({name, age, others}) => ({name, age, ...others})
)(myObj);
console.log(newObj);
For the object of unknown depth you can try using recursion.
Create a function which takes an object and array of keys to be removed as arguments.
Create a helper(which takes 1 object as parameter) function inside main and create empty object.
Loop through the properties of obj using for..in.
check if key is not present in the array of keys to be removed then
Check if the value is object then call the function recursively
If its not object then add it to the result obj.
At last return the result object.
The code will convert the object of unknown depth to a plain object and you can also remove keys from nested objects.
const myObj = {
name: 'Luke',
age: 12,
height: '163cm',
weight: '60kg',
others: { one: '1', two: '2'}
};
const removed = ['height','weight','one'];
function removeKeys(obj,removed){
const res = {};
function helper(obj){
for(let key in obj){
if(!removed.includes(key)){
if(typeof obj[key] === "object"){
helper(obj[key]);
}
else res[key] = obj[key]
}
}
}
helper(obj)
return res;
}
const res = removeKeys(myObj,removed);
console.log(res)
This question already has answers here:
How to convert an array of objects to object with key value pairs
(7 answers)
How to convert an array of key-value tuples into an object
(14 answers)
Closed 5 years ago.
I would like to turn this:
let myArray = [ {city: "NY"}, {status: 'full'} ];
to this:
let myObj = { city: "NY", status: 'full' };
while I tried this:
let newObj = {};
for (var i = 0; i < myArray.length; i++) {
(function(x) {
newObj = Object.assign(myArray[i]);
})(i);
}
it assigns the last pair to the object
Spread the array into Object#assign:
const myArray = [ {city: "NY"}, {status: 'full'} ];
const myObj = Object.assign({}, ...myArray);
console.log(myObj);
Note: Assign into an empty object. If you omit the empty object, the 1st element of the original array will be mutated (everything will be merged into it).
You could also use Array.reduce() which will give you more fine grain control:
const myArray = [
{ city: 'NY', color: 'blue', rodents: { small: false, medium: false, large: true } },
{ status: 'full', color: 'red' },
{ sandwich: 'flavourful' },
]
// item is each object in your array
const reduced = myArray.reduce((newObj, item) => {
// existing props will be overwritten by newer object entries in the array
// this example is same as Object.assign spread with right to left precedence,
// until you want more custom logic
Object.keys(item).forEach((key) => { newObj[key] = item[key] })
return newObj
}, {})
console.log(reduced)
// you will see `red` overwrite `blue`
EDIT: after examining this answer after a year, I note that it isn't optimized at all for ability to deep clone or deep merge. I recommend studying those aspects closer and to be careful of copying or breaking references if you are working immutably.
There is no issue with this in the above example because all values are primitives.
I would tend to agree with Ori that your question seems to be about creating an indexed object which isn't usually a good plan, but if its necessary to key your object with numbers you can do it like this:
let newObj = {};
myArray.forEach((val, index) => { newObj[index] = val });
let myArray = [ {city: "NY"}, {status: 'full'} ];
let newObj = myArray.reduce((acc, curr) => {
Object.keys(curr).forEach(val => {
acc[val] = curr[val]
})
return acc
}, {})
console.log(newObj)
This syntax is supported in IE according to caniuse.com
Hi all I have an array of objects (20 of them) that I've placed in this format to make columns in an Angular Project (note there are objects in objects). I want to sort them by object with most keys (inside each object in array), to object with least number keys inside object in array) so that when they are displayed in columns it makes the most sense.
I'm mutating a navbar variable to use for a clickable advanced search. Thanks for the help as this is my first big project as a new developer.
var clickableFilters = [
{"State": {
"0": [{value: "liquid"}, {count: 30}]
}
},
{"Country": {
"0": [{value: "USA"}, {count: 50}]
"1": [{value: "Nigeria"}, {count: 90}]
}
},
{"Color": {
"0": [{value: "blue"}, {count: 30}]
"1": [{value: "purple"}, {count: 50}]
"2": [{value: "green"}, {count: 100}]
}
}
]
How do I sort the objects by number of Keys (keys in inner object) so that it ends up (in JavaScript)
[{"Color": {}}, {"Country": {}}, {"State": {}}]
Solution 1
Using a custom comparator for Array.prototype.sort does what you need it to do
let data = [{a:1,b:2,c:3}, {a:1}, {a:1,b:2,c:3,d:4}, {a:1,b:2}]
data.sort((a,b) => Object.keys(a).length - Object.keys(b).length)
console.log(data)
Solution 2
If your data is nested, the child data should be attached to a property of a known name
let data = [
{foo: 'W', bar: {a:1,b:2,c:3}},
{foo: 'X', bar: {a:1}},
{foo: 'Y', bar: {a:1,b:2,c:3,d:4}},
{foo: 'Z', bar: {a:1,b:2}}
]
let keyToSort = "bar";
data.sort((a,b) => Object.keys(a[keyToSort]).length - Object.keys(b[keyToSort]).length)
console.log(data)
Traps and pitfalls
On the other hand, if it is guaranteed to always have exactly one key (perhaps with an unknown/dynamic name), you could write
Object.keys(a[Object.keys(a)[0]]).length
This however is obviously very ugly and error prone (what if it does have more keys - or none at all). If you have control over the data structure, you should think about refactoring it, since an Object with only one key makes not much sense - you could as well just drop one nesting level.
It's your future
You should be in the habit of battling complexity — whenever it rears its stubborn head, grasp your staff and exert an equally stubborn force back upon it.
The first solution above appears somewhat manageable, but the second one starts to get pretty thick. If you break your solution down into tiny reusable parts, you can keep complexity at bay with relative ease.
const ascComparator = (a,b) => a < b ? -1 : a > b ? 1 : 0
// use this if you want to sort data descending instead of ascending
const descComparator = (a,b) => ascComparator(b,a)
const prop = x => y => y[x]
const len = prop('length')
const comp = f => g => x => f (g (x))
const keylen = comp (len) (Object.keys)
let data = [
{foo: 'W', bar: {a:1,b:2,c:3}},
{foo: 'X', bar: {a:1}},
{foo: 'Y', bar: {a:1,b:2,c:3,d:4}},
{foo: 'Z', bar: {a:1,b:2}}
]
// same as solution #2 but much more descriptive
// "sort ascending using keylen of a.bar compared to keylen of b.bar"
data.sort((a,b) => ascComparator(keylen(a.bar), keylen(b.bar)))
console.log(data)
Breaking complexity down is a way of investing in your future. Once you wrap a bit of complexity up in its own procedure, it will always be at your disposal at a later time. Smaller procedures are also easier to test.
Each of the procedures above ascComparator, prop, len, comp, and keylen have immediately apparent intent. You can revisit these at any time and understand them with ease. And as a result of employing them, it makes your sort that much easier to read/write too.
For your data structure you can use sort() like this.
var arrayOfObjects = [
{"least#OfKeysObject": {
key1: 'value',
}
},
{"secondMost#OfKeysObj": {
key1: 'value',
key2: 'value'
}
},
{"most#OfKeysObj": {
key1: 'value',
key2: 'value',
key3: 'value'
}
}
];
var result = arrayOfObjects.sort(function(a, b) {
return Object.keys(b[Object.keys(b)[0]]).length - Object.keys(a[Object.keys(a)[0]]).length;
});
console.log(result)
Try
arrayOfObjects.sort(function(a, b) {
return Object.keys(a).length > Object.keys(b).length ? -1 : 1;
});