I have an array of objects:
Object = {
1 : { name : bob , dinner : pizza },
2 : { name : john , dinner : sushi },
3 : { name : larry, dinner : hummus }
}
I want to be able to search the object/array for where the key is "dinner", and see if it matches "sushi".
I know jQuery has $.inArray, but it doesn't seem to work on arrays of objects. Or maybe I'm wrong. indexOf also seems to only work on one array level.
Is there no function or existing code for this?
If you have an array such as
var people = [
{ "name": "bob", "dinner": "pizza" },
{ "name": "john", "dinner": "sushi" },
{ "name": "larry", "dinner": "hummus" }
];
You can use the filter method of an Array object:
people.filter(function (person) { return person.dinner == "sushi" });
// => [{ "name": "john", "dinner": "sushi" }]
In newer JavaScript implementations you can use a function expression:
people.filter(p => p.dinner == "sushi")
// => [{ "name": "john", "dinner": "sushi" }]
You can search for people who have "dinner": "sushi" using a map
people.map(function (person) {
if (person.dinner == "sushi") {
return person
} else {
return null
}
}); // => [null, { "name": "john", "dinner": "sushi" }, null]
or a reduce
people.reduce(function (sushiPeople, person) {
if (person.dinner == "sushi") {
return sushiPeople.concat(person);
} else {
return sushiPeople
}
}, []); // => [{ "name": "john", "dinner": "sushi" }]
I'm sure you are able to generalize this to arbitrary keys and values!
jQuery has a built-in method jQuery.grep that works similarly to the ES5 filter function from #adamse's Answer and should work fine on older browsers.
Using adamse's example:
var peoples = [
{ "name": "bob", "dinner": "pizza" },
{ "name": "john", "dinner": "sushi" },
{ "name": "larry", "dinner": "hummus" }
];
you can do the following
jQuery.grep(peoples, function (person) { return person.dinner == "sushi" });
// => [{ "name": "john", "dinner": "sushi" }]
var getKeyByDinner = function(obj, dinner) {
var returnKey = -1;
$.each(obj, function(key, info) {
if (info.dinner == dinner) {
returnKey = key;
return false;
};
});
return returnKey;
}
jsFiddle.
So long as -1 isn't ever a valid key.
If you're going to be doing this search frequently, consider changing the format of your object so dinner actually is a key. This is kind of like assigning a primary clustered key in a database table. So, for example:
Obj = { 'pizza' : { 'name' : 'bob' }, 'sushi' : { 'name' : 'john' } }
You can now easily access it like this: Object['sushi']['name']
Or if the object really is this simple (just 'name' in the object), you could just change it to:
Obj = { 'pizza' : 'bob', 'sushi' : 'john' }
And then access it like: Object['sushi'].
It's obviously not always possible or to your advantage to restructure your data object like this, but the point is, sometimes the best answer is to consider whether your data object is structured the best way. Creating a key like this can be faster and create cleaner code.
You can find the object in array with Alasql library:
var data = [ { name : "bob" , dinner : "pizza" }, { name : "john" , dinner : "sushi" },
{ name : "larry", dinner : "hummus" } ];
var res = alasql('SELECT * FROM ? WHERE dinner="sushi"',[data]);
Try this example in jsFiddle.
You can use a simple for in loop:
for (prop in Obj){
if (Obj[prop]['dinner'] === 'sushi'){
// Do stuff with found object. E.g. put it into an array:
arrFoo.push(Obj[prop]);
}
}
The following fiddle example puts all objects that contain dinner:sushi into an array:
https://jsfiddle.net/3asvkLn6/1/
There's already a lot of good answers here so why not one more, use a library like lodash or underscore :)
obj = {
1 : { name : 'bob' , dinner : 'pizza' },
2 : { name : 'john' , dinner : 'sushi' },
3 : { name : 'larry', dinner : 'hummus' }
}
_.where(obj, {dinner: 'pizza'})
>> [{"name":"bob","dinner":"pizza"}]
I had to search a nested sitemap structure for the first leaf item that machtes a given path. I came up with the following code just using .map() .filter() and .reduce. Returns the last item found that matches the path /c.
var sitemap = {
nodes: [
{
items: [{ path: "/a" }, { path: "/b" }]
},
{
items: [{ path: "/c" }, { path: "/d" }]
},
{
items: [{ path: "/c" }, { path: "/d" }]
}
]
};
const item = sitemap.nodes
.map(n => n.items.filter(i => i.path === "/c"))
.reduce((last, now) => last.concat(now))
.reduce((last, now) => now);
If You want to find a specific object via search function just try something like this:
function findArray(value){
let countLayer = dataLayer.length;
for(var x = 0 ; x < countLayer ; x++){
if(dataLayer[x].user){
let newArr = dataLayer[x].user;
let data = newArr[value];
return data;
}
}
return null;
}
findArray("id");
This is an example object:
layerObj = {
0: { gtm.start :1232542, event: "gtm.js"},
1: { event: "gtm.dom", gtm.uniqueEventId: 52},
2: { visitor id: "abcdef2345"},
3: { user: { id: "29857239", verified: "Null", user_profile: "Personal", billing_subscription: "True", partners_user: "adobe"}
}
Code will iterate and find the "user" array and will search for the object You seek inside.
My problem was when the array index changed every window refresh and it was either in 3rd or second array, but it does not matter.
Worked like a charm for Me!
In Your example it is a bit shorter:
function findArray(value){
let countLayer = Object.length;
for(var x = 0 ; x < countLayer ; x++){
if(Object[x].dinner === value){
return Object[x];
}
}
return null;
}
findArray('sushi');
We use object-scan for most of our data processing. It's conceptually very simple, but allows for a lot of cool stuff. Here is how you would solve your question
// const objectScan = require('object-scan');
const findDinner = (dinner, data) => objectScan(['*'], {
abort: true,
rtn: 'value',
filterFn: ({ value }) => value.dinner === dinner
})(data);
const data = { 1: { name: 'bob', dinner: 'pizza' }, 2: { name: 'john', dinner: 'sushi' }, 3: { name: 'larry', dinner: 'hummus' } };
console.log(findDinner('sushi', data));
// => { name: 'john', dinner: 'sushi' }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
Related
let student = [{
id:1,
name:'aman',
class:'10',
gender:'male'
},{
id:2,
name:'shivani',
class:'10',
gender:'female'
},{
id:2,
name:'riyan',
class:'11',
gender:'female'
}]
function customFilter(objList, text){
if(undefined === text || text === '' ) return objList;
return objList.filter(product => {
let flag;
for(let prop in product){
if(product[prop].toString().indexOf(text)>-1){
product[prop] = 0
product[prop]++
flag = product[prop]
console.log(flag)
}
}
return flag;
});}
console.log( customFilter(student, '10'))
I want the output of the number of students in a class. Example: when I pass class 10 as an argument then i should get output how many students in class 10
output:
{class:10,stduent:5 }
I didn't get your question well, but I assumed you want number of student in a class like this {class:10, students: 2}
let student = [
{ id:1, name:'aman', class:'10', gender:'male'},
{ id:2, name:'shivani', class:'10', gender:'female' },
{ id:3, name:'riyan', class:'11', gender:'female' }
]
function customFilter(objList, text){
if(undefined === text || text === '' ) return objList;
const numberOfStudents = objList.filter(product => {
for (let prop in product) {
if (product[prop].toString().includes(text)) {
return true;
}
}
});
return {class:text, student:numberOfStudents.length }
}
console.log( customFilter(student, '10'))
If that's the case this code will do , hope it helps
This would also work:
let students = [
{ id: 1, name: "aman", class: "10", gender: "male" },
{ id: 2, name: "shivani", class: "10", gender: "female" },
{ id: 2, name: "riyan", class: "11", gender: "female" },
];
const customFilter = (students, key, value) => {
const res = { [key]: value, student: 0 };
students.forEach((student) => {
if (student[key] === value) {
res.student += 1;
}
});
return res;
};
console.log(customFilter(students, "class", "10"));
Using Array.prototype.forEach()
There are few problems with the code. change class:'10' to grade: 10,.
don't use "class" as a variable name. might cause a few errors
There is a missing ,
numbers shouldn't be inside quotes because the number will be stored as a string
let student = [
{ id: 1, name: 'aman', grade: 10, gender: 'male'},
{ id: 2, name: 'shivani', grade: 10, gender: 'female' },
{ id: 2, name: 'riyan', grade: 11, gender: 'female' },
]
function customFilter(objList, value){
if(!value || value === '') return objList;
let count = 0
objList.forEach(obj => {
const { grade } = obj;
if(grade === value){
count++;
}
})
return {grade: 10, count};
}
console.log(customFilter(student, 10));
and forEach can be used instead of filter. It loops from start to end of an array
Use .reduce() to group all objects that match.
/* hits (accumulator) is initially an empty array.
now (current) is the object of the current iteration. */
array.reduce((hits, now) => { //...
On each iteration, get all of the current object's values (in lower case) in an array.
Object.values(now).map(val => val.toLowerCase())
/* result of the first object: ["01gn3z1ryjjqhn588ax3bws6qb", "theo bramstom",
"genderqueer", "english"] */
If any of the values of the current object matches the given string (term), add the current object to the hits array.
if (Object.values(now)
.map(val => val.toLowerCase()).includes(term.toLowerCase())) {
hits.push(now);
}
An object literal is returned.
{
"matches": /* an array of all matched objects */,
"total": /* the .length of "matches" array */
};
/* To get the answer prompted in OP -- do the following */
const x = dataFilter(students, "Math");
console.log(x.total);
// NOTE: key "class" is now "subject" just for aesthetics
const students=[{id:"01GN3Z1RYJJQHN588AX3BWS6QB",name:"Theo Bramstom",gender:"Genderqueer",subject:"English"},{id:"01GN3Z1RYM527HAX56ZN14F0YB",name:"Juli Marsy",gender:"Female",subject:"History"},{id:"01GN3Z1RYPYP1FFFEY55T92VX2",name:"Linc Espley",gender:"Non-binary",subject:"Math"},{id:"01GN3Z1RYR325M0QETVVPE2N5J",name:"Barbabas Grisley",gender:"Male",subject:"Math"},{id:"01GN3Z1RYTXA49SBQYXR9DMC04",name:"Godfree Braybrook",gender:"Male",subject:"English"},{id:"01GN3Z1RYVE4N5D16C8QWB1XGF",name:"Jason De Vuyst",gender:"Male",subject:"History"},{id:"01GN3Z1RYXY9WXF1Y407HXFYH8",name:"Adler McCanny",gender:"Male",subject:"Math"},{id:"01GN3Z1RYY9XV444J0SP5Y0QC2",name:"Noellyn MacMorland",gender:"Genderqueer",subject:"Math"},{id:"01GN3Z1RZ0HPQNZ1VKX8ZHA9ZY",name:"Padget Geldeford",gender:"Male",subject:"Math"},
{id:"01GN3Z1RZ2DZE92NG42KSGDXN9",name:"Milissent Treby",gender:"Female",subject:"Art"}];
const dataFilter = (array, term) => {
let result = array.reduce((hits, now) => {
if (Object.values(now).map(val => val.toLowerCase()).includes(term.toLowerCase())) {
hits.push(now);
}
return hits;
}, []);
return {"matches": result, "total": result.length};
}
console.log(dataFilter(students, "Math"));
// Control case: term === "Math"
console.log(dataFilter(students, "PE"));
// No match case: term != "PE"
console.log(dataFilter(students, "female"));
// Case insensitive case: term === "Female"
First time posting on here and was hoping to get some help I can't seem to figure out how to do this problem. It's basically to create a function that receives an array of objects that returns a new object.
For some reason, push won't go through and returns the error property of push is undefined.
const organizeInstructors = function(instructors) {
let output = {}; // so the obvious which is to create the object
for (let i = 0; i < instructors.length; i++) {
if (!output[instructors[course]]) {
output[instructors[course]] = instructors[course];
} else {
output[instructors[course]].push(instructors[name]);
}
}
return output;
};
console.log(organizeInstructors([{
name: "Samuel",
course: "iOS"
},
{
name: "Victoria",
course: "Web"
},
{
name: "Karim",
course: "Web"
},
{
name: "Donald",
course: "Web"
}
]));
expected output
{
iOS: ["Samuel"],
Web: ["Victoria", "Karim", "Donald"]
}
Thanks for any advice or hints you guys can give
Looks like you were getting a bit confused iterating over arrays vs keying into objects.
let organizeInstructors = function(instructors) {
let output = {}; // so the obvious which is to create the object
for(let i = 0; i < instructors.length; i++) {
const instructor = instructors[i]
if(!output[instructor.course]) {
output[instructor.course] = []
}
output[instructor.course].push(instructor.name)
}
return output;
}
console.log(organizeInstructors([
{name: "Samuel", course: "iOS"},
{name: "Victoria", course: "Web"},
{name: "Karim", course: "Web"},
{name: "Donald", course: "Web"}
]))
Adding the const instructor makes it much easier to read as well
This uses the Array.prototype.reduce method.
Please note that this will not check if the value is already present in the course array and will just blindly add in. This could mean that you get multiple instances of the same name in the same course.
const organizeInstructors = function(instructors) {
return instructors.reduce((cumulative, current) => {
// if we don't have a course in cumulative object, add it.
if (!cumulative[current.course]) cumulative[current.course] = [];
// add in the current name.
cumulative[current.course].push(current.name);
// return the cumulative object for the next iteration.
return cumulative;
}, {});
}
console.log(organizeInstructors([{
name: "Samuel",
course: "iOS"
},
{
name: "Victoria",
course: "Web"
},
{
name: "Karim",
course: "Web"
},
{
name: "Donald",
course: "Web"
}
]));
Using reduce
data = [ { name: "Samuel", course: "iOS" }, { name: "Victoria", course: "Web" }, { name: "Karim", course: "Web" }, { name: "Donald", course: "Web" }, ];
getObj = (data) =>
data.reduce(
(r, c) => (
!r[c.course] // checks if accumulator doesn't have c.course as key
? ((r[c.course] = []), r[c.course].push(c.name)) // then make an array that corresponds the key then push c.name
: r[c.course].push(c.name), // else push c.name to the corresponding array
r
),
{}
);
console.log(getObj(data));
I have the object coming in from server as shown below :-
[
{
"gender": {
"male": 25,
"female": 22
},
"population": {
"pop_under": -23 ,
"pop_over": 10,
}
}
]
I want to extract the key as nameand value as value for above object which after modification looks like this :-
[
{
gender : [
{
name : "male",
value : 25
},
{
name : "female",
value : 22
}
],
population : [
{
name: "pop_under" ,
value : "-23"
},
{
name: "pop_over" ,
value : "10"
}
]
}
]
How can perform said modification? And is it useful to use lodashpackage for this ?
Thank you very much for your help.
You can user reducer for creating new object and Object.keys for looping object fields:
const newData = data.reduce((acc,rec) => {
return [...acc, Object.keys(rec).reduce((iacc,irec)=>{
return {...iacc,
[irec]: (Object.keys(rec[irec]).map((item)=>{
return {name: item, value: rec[irec][item]}
}))
}
},{})]
}, [])
See full example in the playground: https://jscomplete.com/playground/s528735
Using .map and Object.keys method.
let result =[];
data.forEach(obj => {
Object.keys(obj).forEach(key=>{
let response = Object.
keys(obj[key]).
map(property=> (
{
name: `${property}`,
value:obj[key][property]
}
)
);
result.push({[key]:response});
});
});
console.log(result);
Sorry for the title, It's limited to 150 characters.
Full code example:
https://jsfiddle.net/c81zw30m/
Data:
Let's say I make an API request and I get this JSON object returned:
[
{
id: 123,
person: {
data: {
name: 'John',
language: 'Javascript'
}
},
details: {
age: 25
},
has_experience: true
},
{
id: 456,
person: {
data: {
name: 'Peter',
language: null // here we have null as a value.
}
},
details: {
age: 40
},
has_experience: false
},
{
id: 789,
person: {
data: {
name: 'Paul',
language: 'Python'
}
},
details: {
age: 30
},
has_experience: null // and here we also don't know if the person is available
},
];
Goal:
The end goal here is to iterate over the array and end up with new array of objects with different key names. Say for example I want to replace the key of person with human or the key of available with availability.
Additionally (optionally) we want to skip adding keys which value is equal to null.
Current solution:
let results = [];
for (let i=0; i< json.length; i++) {
results.push({
user_id: json[i].id,
name: json[i].person.data.name,
age: json[i].details.age,
has_experience: json[i].available ? json[i].available : false // here we are assigning a value no matter what using a ternary operator, what if we want no key:value pair here, just skip that pair
});
if (json[i].person.data.language) { results[i].language = json[i].person.data.language }
}
console.log(results);
Problem:
Now the example and solution I provided works, but imagine if the original API request had hundreds of key:value pairs, and many of them might be of null value.
Question:
Using modern javascript, is there any less verbose and more clean looking/elegant way to handle this problem?
Overall I am looking to create a brand new array of objects based on the original one, but with new key names where necessary. Additionally, we want to skip adding some of them if the value of the key is null for example.
Cheers.
EDIT:
Changed the key name from the example originally provided from available to has_experience because it was a bit misleading. I am not looking to filter out the original array of objects based on the value of a given key. If I wanted to do that I'd start with filter and then chain on.
What I want to do is to omit adding a key:value pair in the newly formed array if the value of the key is null for example.
Using lodash (or similar), you could get your mapping definition out of the mapping loop.
I find the following reasonably concise, though it can probably be shortened a little further.
import { get, set } from "lodash";
let json = [ ... ];
let mapping = new Map([
["user_id", "id"],
["name", "person.data.name"],
["age", "details.age"],
["availability", "available"],
["language", "person.data.language"],
["some.nested.property", "person.data.language"]
]);
var results = json.map(element => {
var mappedElement = {};
mapping.forEach((path, field, map) => {
var value = get(element, path);
if (value) {
set(mappedElement, field, value);
}
});
return mappedElement;
});
console.log(results);
Running this on your data yields
[Object, Object, Object]
0: Object
user_id: 123
name: "John"
age: 25
availability: true
language: "Javascript"
some: Object
nested: Object
property: "Javascript"
1: Object
user_id: 456
name: "Peter"
age: 40
2: Object
user_id: 789
name: "Paul"
age: 30
language: "Python"
some: Object
nested: Object
property: "Python"
Working example: https://codesandbox.io/s/mnkp79668
You can try something like this
You can achieve with map()
let json = [{id: 123, person: { data: { name: 'John', language: 'Javascript' } }, details: { age: 25 }, has_experience: true },
{id: 456, person: { data: { name: 'Peter',language: null } }, details: { age: 40 }, has_experience: false},
{id: 789, person: { data: { name: 'Paul', language: 'Python' } }, details: { age: 30 }, has_experience: null },];
let results = [];
results = json.map(current => {
let temp = {
user_id: current.id,
name: current.person.data.name,
age: current.details.age,
}
if (current.has_experience) {
temp.availablity = current.has_experience
}
if (current.person.data.language)
{ temp.language = current.person.data.language }
return temp;
})
console.log(results);
You have two separate problems to resolve. The first appears to be a requirement for generic flattening of the nested data structures within the input with out specifying every possible key that might exist.
This function will recursively flatten a nested object, along the way omitting any null values. However, this function might overwrite any values where the same key exists at multiple levels, so see below.
function flatten(obj, dest) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
flatten(obj[key], dest);
} else if (obj[key] !== null) {
dest[key] = obj[key];
}
}
return dest;
}
You also want to re-map some of the keys in your data, where the below function can be used both as a pre-processor to convert known duplicate keys into unique keys, and can also be used as a post-processor to convert particular keys back into nested objects. NB: requires "lodash".
function remap(obj, keys) {
for (let [in_key, out_key] of keys) {
let val = _.get(obj, in_key, null);
if (val !== null) {
_.unset(obj, in_key);
_.set(obj, out_key, val);
}
}
return obj;
}
The functions can be chained together like this:
let in_map = new Map([
['user.id', 'user_id']
]);
let out_map = new Map([
['available', 'test.availability']
]);
let out = data.map(obj => remap(obj, in_map))
.map(obj => flatten(obj, {}))
.map(obj => remap(obj, out_map));
I think what you want is to first filter the list, then map over the filtered results to create the new structure. This may not be especially performant however if the list is quite large.
const list = [
{
id: 123,
person: {
data: {
name: 'John',
language: 'Javascript'
}
},
details: {
age: 25
},
available: true
},
{
id: 456,
person: {
data: {
name: 'Peter',
language: null // here we have null as a value.
}
},
details: {
age: 40
},
available: false
},
{
id: 789,
person: {
data: {
name: 'Paul',
language: 'Python'
}
},
details: {
age: 30
},
available: null // and here we also don't know if the person is available
},
];
const newList = list.filter(listItem => listItem.available).map(filteredItem => {
return {
user_id: filteredItem.id,
name: filteredItem.person.data.name,
age: filteredItem.details.age,
availability: !!filteredItem.available
}
})
document.getElementById('list').innerText = JSON.stringify(list, null, 2);
document.getElementById('newList').innerText = JSON.stringify(newList, null, 2);
.container {
display: flex;
}
.container pre {
flex: 0 0 50%;
}
<div class="container">
<pre id="list"></pre>
<pre id="newList"></pre>
</div>
I am recently started using lodash, I want to merge following objects.
Object:
var firstObj =
{
"1" : [
{
name: "karthick",
age : 25
},
{
name: "arun",
age : 20
}
],
"2" : [
{
name: "varun",
age : 25
},
{
name: "smith",
age : 20
}
]
}
var secondObj =
{
"1" : [
{
name: "gautham",
age : 17
},
{
name: "mathan",
age : 19
}
],
"2" : [
{
name: "ashok",
age : 16
},
{
name: "dharani",
age : 20
}
]
}
Merged Object / Result:
{
"1" : [
{
name: "karthick",
age : 25
},
{
name: "arun",
age : 20
},
{
name: "gautham",
age : 17
},
{
name: "mathan",
age : 19
}
]
"2" : [
{
name: "varun",
age : 25
},
{
name: "smith",
age : 20
},
{
name: "ashok",
age : 16
},
{
name: "dharani",
age : 20
}
]
}
What i had tried:
Fiddle
Any suggestion will be grateful
In the documentation you can read you have to use a customizer function that concats the arrays for you. (https://lodash.com/docs#mergeWith)
This method looks like this:
function customizer(objValue, srcValue) {
if (_.isArray(objValue)) {
return objValue.concat(srcValue);
}
}
Here's it used in your example:
https://jsfiddle.net/82koarwq/
If you want to better understand what's happening inside the merge method:
Merge takes the first object that it is passed as a base. It then loops through all the keys in the other objects it is passed (in order of arguments). It does two things:
If the first object does not contain the key, it adds the key and its value.
If the first object already contains the key, it overwrites its value.
By using mergeWith, you have the opportunity to overwrite this behavior. You can pass a method that is used to deal with the second case. If you want to fall back on the default merge behavior, you'll have to make your method return undefined.
Try this updated fiddle
var output = {};
var firstObjKeys = Object.keys(firstObj);
var secondObjKeys = Object.keys(secondObj).filter(function(val){
return firstObjKeys.indexOf(val) == -1;
});
var allKeys = firstObjKeys.concat(secondObjKeys);
allKeys.forEach(function(val){
var firstArr = firstObj[val] || [];
var secondArr = secondObj[val] || [];
output[val] = firstArr.concat(secondArr);
})
document.body.innerHTML += JSON.stringify(output,0,4)