JS search in object values - javascript

I have an array of homogeneous objects like so;
[
{
"foo" : "bar",
"bar" : "sit"
},
{
"foo" : "lorem",
"bar" : "ipsum"
},
{
"foo" : "dolor",
"bar" : "amet"
}
]
I'd like to search these objects' values (not keys) with a keyword, and return an array of objects that contain the keyword in any of the values.
So for example, with a keyword r, I would get all the objects ("baR" in object #1, "loRem" in object #2 and "doloR" in object #3). With a keyword lo, I would get objects 2 and 3 ("LOrem" and "doLOr"), with a, I'd get objects 1 and 3, ("bAr" and "Amet"). With the keyword foo however, I would get an empty array, since "foo" is a key, and isn't found in any of the values (unlike "bar")... you get the idea.
How would I go about doing this? Thanks a lot in advance!

Something like this:
var objects = [
{
"foo" : "bar",
"bar" : "sit"
},
{
"foo" : "lorem",
"bar" : "ipsum"
},
{
"foo" : "dolor",
"bar" : "amet"
}
];
var results = [];
var toSearch = "lo";
for(var i=0; i<objects.length; i++) {
for(key in objects[i]) {
if(objects[i][key].indexOf(toSearch)!=-1) {
results.push(objects[i]);
}
}
}
The results array will contain all matched objects.
If you search for 'lo', the result will be like:
[{ foo="lorem", bar="ipsum"}, { foo="dolor", bar="amet"}]
NEW VERSION - Added trim code, code to ensure no duplicates in result set.
function trimString(s) {
var l=0, r=s.length -1;
while(l < s.length && s[l] == ' ') l++;
while(r > l && s[r] == ' ') r-=1;
return s.substring(l, r+1);
}
function compareObjects(o1, o2) {
var k = '';
for(k in o1) if(o1[k] != o2[k]) return false;
for(k in o2) if(o1[k] != o2[k]) return false;
return true;
}
function itemExists(haystack, needle) {
for(var i=0; i<haystack.length; i++) if(compareObjects(haystack[i], needle)) return true;
return false;
}
var objects = [
{
"foo" : "bar",
"bar" : "sit"
},
{
"foo" : "lorem",
"bar" : "ipsum"
},
{
"foo" : "dolor blor",
"bar" : "amet blo"
}
];
function searchFor(toSearch) {
var results = [];
toSearch = trimString(toSearch); // trim it
for(var i=0; i<objects.length; i++) {
for(var key in objects[i]) {
if(objects[i][key].indexOf(toSearch)!=-1) {
if(!itemExists(results, objects[i])) results.push(objects[i]);
}
}
}
return results;
}
console.log(searchFor('lo '));

All the other old answers use a for in loop, modern JavaScript has Object.keys. Combine that with some, includes, and filter and it is a bit nicer.
var a = [{
name: 'xyz',
grade: 'x'
}, {
name: 'yaya',
grade: 'x'
}, {
name: 'x',
frade: 'd'
}, {
name: 'a',
grade: 'b'
}];
function filterIt(arr, searchKey) {
return arr.filter(function(obj) {
return Object.keys(obj).some(function(key) {
return obj[key].includes(searchKey);
})
});
}
console.log("find 'x'", filterIt(a,"x"));
console.log("find 'a'", filterIt(a,"a"));
console.log("find 'z'", filterIt(a,"z"));
Or with ES6
function filterIt(arr, searchKey) {
return arr.filter(obj => Object.keys(obj).some(key => obj[key].includes(searchKey)));
}

This is a cool solution that works perfectly
const array = [{"title":"tile hgfgfgfh"},{"title":"Wise cool"},{"title":"titlr DEytfd ftgftgfgtgtf gtftftft"},{"title":"This is the title"},{"title":"yeah this is cool"},{"title":"tile hfyf"},{"title":"tile ehey"}];
var item = array.filter(item=>item.title.toLowerCase().includes('this'));
alert(JSON.stringify(item))
EDITED
const array = [{"title":"tile hgfgfgfh"},{"title":"Wise cool"},{"title":"titlr DEytfd ftgftgfgtgtf gtftftft"},{"title":"This is the title"},{"title":"yeah this is cool"},{"title":"tile hfyf"},{"title":"tile ehey"}];
// array.filter loops through your array and create a new array returned as Boolean value given out "true" from eachIndex(item) function
var item = array.filter((item)=>eachIndex(item));
//var item = array.filter();
function eachIndex(e){
console.log("Looping each index element ", e)
return e.title.toLowerCase().includes("this".toLowerCase())
}
console.log("New created array that returns \"true\" value by eachIndex ", item)

This is a proposal which uses the key if given, or all properties of the object for searching a value.
function filter(array, value, key) {
return array.filter(key
? a => a[key] === value
: a => Object.keys(a).some(k => a[k] === value)
);
}
var a = [{ name: 'xyz', grade: 'x' }, { name: 'yaya', grade: 'x' }, { name: 'x', frade: 'd' }, { name: 'a', grade: 'b' }];
console.log(filter(a, 'x'));
console.log(filter(a, 'x', 'name'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

This is a succinct way with modern Javascript:
var objects = [
{
"foo" : "bar",
"bar" : "sit"
},
{
"foo" : "lorem",
"bar" : "ipsum"
},
{
"foo" : "dolor blor",
"bar" : "amet blo"
}
];
const query = "lo";
const filteredItems = objects.filter(item => `${item.foo} ${item.bar}`.includes(query));

The search function will return all objects which contain a value which has contains the search query
function search(arr, s){
var matches = [], i, key;
for( i = arr.length; i--; )
for( key in arr[i] )
if( arr[i].hasOwnProperty(key) && arr[i][key].indexOf(s) > -1 )
matches.push( arr[i] ); // <-- This can be changed to anything
return matches;
};
// dummy data
var items = [
{
"foo" : "bar",
"bar" : "sit"
},
{
"foo" : "lorem",
"bar" : "ipsum"
},
{
"foo" : "dolor",
"bar" : "amet"
}
];
var result = search(items, 'lo'); // search "items" for a query value
console.log(result); // print the result

Modern Javascript 😄
const objects = [
{
"foo" : "bar",
"bar" : "sit"
},
{
"foo" : "lorem",
"bar" : "ipsum"
},
{
"foo" : "dolor blor",
"bar" : "amet blo"
}
];
const keyword = 'o';
const results = objects.filter(object => Object.values(object).some(i => i.includes(keyword)));
console.log(results);
// results [{ foo: 'lorem', bar: 'ipsum' },{ foo: 'dolor blor', bar: 'amet blo' }]

var search(subject, objects) {
var matches = [];
var regexp = new RegExp(subject, 'g');
for (var i = 0; i < objects.length; i++) {
for (key in objects[i]) {
if (objects[i][key].match(regexp)) matches.push(objects[i][key]);
}
}
return matches;
};
var items = [
{
"foo" : "bar",
"bar" : "sit"
},
{
"foo" : "lorem",
"bar" : "ipsum"
},
{
"foo" : "dolor",
"bar" : "amet"
}
];
search('r', items); // ["bar", "lorem", "dolor"]

As a Javascripter Lv. 1 I just learned to search for strings in objects with this:
function isThere( a_string, in_this_object )
{
if( typeof a_string != 'string' )
{
return false;
}
for( var key in in_this_object )
{
if( typeof in_this_object[key] == 'object' || typeof in_this_object[key] == 'array' )
{
if ( isThere( a_string, in_this_object[key] ) )
{
return true;
}
}
else if( typeof in_this_object[key] == 'string' )
{
if( a_string == in_this_object[key] )
{
return true;
}
}
}
return false;
}
I know is far from perfect but it is useful.
Feel free to comment in order to improve this.

search(searchText) {
let arrayOfMatchedObjects = arrayOfAllObjects.filter(object => {
return JSON.stringify(object)
.toString()
.toLowerCase()
.includes(searchText);
});
return arrayOfMatchedObjects;
}
This could be very simple, easy, fast and understandable Search function for some of you just like me.

Although a bit late, but a more compact version may be the following:
/**
* #param {string} quickCriteria Any string value to search for in the object properties.
* #param {any[]} objectArray The array of objects as the search domain
* #return {any[]} the search result
*/
onQuickSearchChangeHandler(quickCriteria, objectArray){
let quickResult = objectArray.filter(obj => Object.values(obj).some(val => val?val.toString().toLowerCase().includes(quickCriteria):false));
return quickResult;
}
It can handle falsy values like false, undefined, null and all the data types that define .toString() method like number, boolean etc.

You can use this javascript lib, DefiantJS (http://defiantjs.com), with which you can filter matches using XPath on JSON structures. To put it in JS code:
var data = [
{ "foo": "bar", "bar": "sit" },
{ "foo": "lorem", "bar": "ipsum" },
{ "foo": "dolor", "bar": "amet" }
],
res1 = JSON.search( data, '//*[contains(name(), 'r')]/..' ),
res2 = JSON.search( data, '//*[contains(., 'lo')]' );
/*
res1 = [
{ "foo": "bar", "bar": "sit" },
{ "foo": "lorem", "bar": "ipsum" },
{ "foo": "dolor", "bar": "amet" }
]
*/
/*
res2 = [
{ "foo": "lorem", "bar": "ipsum" },
{ "foo": "dolor", "bar": "amet" }
]
*/
Here is a working fiddle;
http://jsfiddle.net/hbi99/2kHDZ/
DefiantJS extends the global object with the method "search" and returns an array with matches (empty array if no matches were found). You can try out the lib and XPath queries using the XPath Evaluator here:
http://www.defiantjs.com/#xpath_evaluator

I needed to perform a search on a large object and return the addresses of the matches, not just the matched values themselves.
This function searches an object for a string (or alternatively, uses a callback function to perform custom logic) and keeps track of where the value was found within the object. It also avoids circular references.
//Search function
var locateInObject = function(obj, key, find, result, currentLocation){
if(obj === null) return;
result = result||{done:[],found:{}};
if(typeof obj == 'object'){
result.done.push(obj);
}
currentLocation = currentLocation||key;
var keys = Object.keys(obj);
for(var k=0; k<keys.length; ++k){
var done = false;
for(var d=0; d<result.done.length; ++d){
if(result.done[d] === obj[keys[k]]){
done = true;
break;
}
}
if(!done){
var location = currentLocation+'.'+keys[k];
if(typeof obj[keys[k]] == 'object'){
locateInObject(obj[keys[k]], keys[k], find, result, location)
}else if((typeof find == 'string' && obj[keys[k]].toString().indexOf(find) > -1) || (typeof find == 'function' && find(obj[keys[k]], keys[k]))){
result.found[location] = obj[keys[k]];
}
}
}
return result.found;
}
//Test data
var test = {
key1: {
keyA: 123,
keyB: "string"
},
key2: {
keyC: [
{
keyI: "string123",
keyII: 2.3
},
"string"
],
keyD: null
},
key3: [
1,
2,
123,
"testString"
],
key4: "123string"
}
//Add a circular reference
test.key5 = test;
//Tests
console.log(locateInObject(test, 'test', 'string'))
console.log(locateInObject(test, 'test', '123'))
console.log(locateInObject(test, 'test', function(val, key){ return key.match(/key\d/) && val.indexOf('string') > -1}))

Came across this problem today and using a modified version of the provided code by epascarello in this thread did the trick, because that version had trouble when the object contained some values others than strings (like a number of booleans for example).
console.log('find: ', findIn(arrayOfObjects, searchKey));
const findIn = (arr, searchKey) => {
return arr.filter(obj =>
Object.keys(obj).some(key => {
if (typeof obj[key] === 'string') {
return obj[key].includes(searchKey);
}
})
);
};

Here is the answer in 100% PURE JavaScript:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title></title>
<script type="text/javascript">
var mySet = [{
"foo": "bar",
"bar": "sit"
},
{
"foo": "lorem",
"bar": "ipsum"
},
{
"foo": "dolor",
"bar": "amet"
}
];
function queryObject(needle, set) {
var results = new Array();
for (index = 0; index < set.length; index++) {
for (key in set[index]) {
if (set[index][key].indexOf(needle) > -1) {
results.push(set[index]);
}
}
}
if (results.length) {
return JSON.stringify(results);
} else {
return "No match!";
}
}
</script>
</head>
<body>
<form>
<input type="text" id="prompt" onFocus="this.value='';" value="Type your query HERE" size="20" onKeyDown="document.getElementById('submit').disabled = false;">
<input id="submit" type="button" value="Find in Object" onClick="var prompt=document.getElementById('prompt'); if(prompt.value){document.getElementById('output').innerHTML = queryObject(prompt.value, mySet);}else{prompt.value='Type your query HERE';}"
disabled="disabled">
<div id="output"></div>
</form>
</body>
</html>
There are, of course, more fancy ways to traverse your object using JQuery, but this is the basic concept.
Cheers!
*EDIT: Sorry, I didn't read your question carefully enough, and modified the code to return an array of objects as you requested.

MAKE SIMPLE
const objects = [
{
"foo" : "bar",
"bar" : "sit",
"date":"2020-12-20"
},
{
"foo" : "lorem",
"bar" : "ipsum",
"date":"2018-07-02"
},
{
"foo" : "dolor",
"bar" : "amet",
"date":"2003-10-08"
},
{
"foo" : "lolor",
"bar" : "amet",
"date":"2003-10-08"
}
];
const filter = objects.filter(item => {
const obj = Object.values(item)
return obj.join("").indexOf('2003') !== -1
})
console.log(filter)

Just another variation using ES6, this is what I use.
// searched keywords
const searchedWord = "My searched exp";
// array of objects
let posts = [
{
text_field: "lorem ipsum doleri imet",
_id: "89789UFJHDKJEH98JDKFD98"
},
{
text_field: "ipsum doleri imet",
_id: "JH738H3JKJKHJK93IOHLKL"
}
];
// search results will be pushed here
let matches = [];
// regular exp for searching
let regexp = new RegExp(searchedWord, 'g');
// looping through posts to find the word
posts.forEach((post) => {
if (post["text_field"].match(regexp)) matches.push(post);
});

Below shared for specific given property
searchContent:function(s, arr,propertyName){
var matches = [];
var propertyNameString=this.propertyNameToStr(propertyName);
for (var i = arr.length; i--; ){
if((""+Object.getOwnPropertyDescriptor(arr[i], propertyNameString).value).indexOf(s) > -1)
matches.push(arr[i]);
}
return matches;
},
propertyNameToStr: function (propertyFunction) {
return /\.([^\.;]+);?\s*\}$/.exec(propertyFunction.toString())[1];
}
//usage as below
result=$localStorage.searchContent(cabNo,appDataObj.getAll(),function() { dummy.cabDriverName; })

I've found a way that you can search in nested object like everything search , for example list of student that have nested lesson object:
var students=[{name:"ali",family:"romandeh",age:18,curse:[
{lesson1:"arabic"},
{lesson2:"english"},
{lesson3:"history"}
]},
{name:"hadi",family:"porkar",age:48,curse:[
{lesson1:"arabic"},
{lesson2:"english"},
{lesson3:"history"}
]},
{name:"majid",family:"porkar",age:30,curse:[
{lesson1:"arabic"},
{lesson2:"english"},
{lesson3:"history"}
]}
];
function searchInChild(objects, toSearch){
var _finded=false;
for(var i=0; i<objects.length; i++) {
for(key in objects[i]) {
if(objects[i][key]!=null && typeof(objects[i][key] )!="boolean" && typeof(objects[i][key] )!="number"){
if (typeof objects[i][key] == 'object') {
_finded= searchInChild(objects[i][key],toSearch);
}
else if(objects[i][key].indexOf(toSearch)!=-1) {
_finded=true;
}
}
}
}
return _finded;
}
function findNested(objects, toSearch) {
var _results=[];
for(var i=0; i<objects.length; i++) {
for(key in objects[i]) {
if(objects[i][key]!=null && typeof(objects[i][key] )!="boolean" && typeof(objects[i][key] )!="number"){
if (typeof objects[i][key] == 'object') {
if(searchInChild(objects[i][key],toSearch)){
_results.push(objects[i]);
}
}
else if(objects[i][key].indexOf(toSearch)!=-1) {
_results.push(objects[i]);
}
}
}
}
return _results;
}
$('.quickSearch').on('click',function(){
var _inputSeach=$('#evertingSearch').val();
if(_inputSeach!=''){
var _finded=findNested(students,_inputSeach);
$('.count').html(_finded.length);}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<head>
</head>
<body>
<span>
<pre><code>
var students=[{name:"ali",family:"romandeh",age:18,curse:[
{lesson1:"arabic"},
{lesson2:"english"},
{lesson3:"history"}
]},
{name:"hadi",family:"porkar",age:48,curse:[
{lesson1:"arabic"},
{lesson2:"english"},
{lesson3:"history"}
]},
{name:"majid",family:"rezaeiye",age:30,curse:[
{lesson1:"arabic"},
{lesson2:"english"},
{lesson3:"history"}
]}
];
</code></pre>
<span>
<input id="evertingSearch" placeholder="Search on students" />
<input type="button" class="quickSearch" value="search" />
<lable>count:</lable><span class="count"></span>
</body>
</html>

I have created this easy to use library that does exactly what you are looking for: ss-search
import { search } from "ss-search"
const data = [
{
"foo" : "bar",
"bar" : "sit"
},
{
"foo" : "lorem",
"bar" : "ipsum"
},
{
"foo" : "dolor",
"bar" : "amet"
}
]
const searchKeys = ["foor", "bar"]
const searchText = "dolor"
const results = search(data, keys, searchText)
// results: [{ "foo": "dolor", "bar": "amet" }]

You can use the _filter method of lodash:
return _filter((item) => item.name.includes("fo"),tempObjectHolder);

you can use modern js with spesific key
const filter = (array, value, key) => {
return array.filter(
key
? (a) => a[key].toLowerCase().includes(value.toLowerCase())
: (a) =>
Object.keys(a).some((k) =>
a[k].toLowerCase().includes(value.toLowerCase())
)
)
}
const data = [
{
"foo" : "bar",
"bar" : "sit"
},
{
"foo" : "lorem",
"bar" : "ipsum"
},
{
"foo" : "dolor blor",
"bar" : "amet blo"
}
];
filter(data, 'o', 'foo')
// results [{ foo: 'lorem', bar: 'ipsum' },{ foo: 'dolor blor', bar: 'amet blo' }]

Related

Comparing two nested JSON structures

I'm trying to compare two JSON structures by their keys, not the values inside the keys.
The goal is to see if the one is different by the other. I don't need a diff, just a simple flag. I've tried to do it but somehow I can't get a solution that would not be overcomplicated.
Sample JSON1 structure
{ "parentKey" :
{ "childKey" : { "nestedKey" : "value" },
"childKey2: "value2" }}
JSON2
{ "parentKey" :
{ "childKey" : { "nestedKey" : "value2",
"nestedKey2": "value3"},
"childKey2: "value2" }}
If you only need to see if the 2 objects are not exactly identical (just the keys, not the values) It's pretty simple, you just check Every key in both objects. If you need to know what key is different or how is different it's entirely a different problem.
let o1 = { "parentKey" :
{ "childKey" : { "nestedKey" : "value" },
"childKey2": "value2" }}
let o2 = { "parentKey" :
{ "childKey" : { "nestedKey" : "value2",
"nestedKey2": "value3"},
"childKey2": "value2" }}
let keysInObj1 = []
let keysInObj2 = []
function compare(object, arr){
for (const obj in object){
arr.push(obj)
if(typeof(object[obj]) == 'object'){
compare(object[obj], arr)
}
}
}
compare(o1, keysInObj1)
compare(o2, keysInObj2)
keysInObj1.forEach((val, index)=>{
if(keysInObj2[index] != val){
console.log('Objects are not the same')
}
})
JsFiddle
Here is an iterative solution using object-scan
This solution could easily be modified to tell you exactly the keys that the objects are different.
// const objectScan = require('object-scan');
const d1 = { parentKey: { childKey: { nestedKey: 'value' }, childKey2: 'value2' } };
const d2 = { parentKey: { childKey: { nestedKey: 'value2', nestedKey2: 'value3' }, childKey2: 'value2' } };
const cmp = (a, b) => {
const r1 = objectScan(['**'], { joined: true })(a);
const r2 = objectScan(['**'], { joined: true })(b);
if (r1.length !== r2.length) {
return false;
}
return r1.every((e) => r2.includes(e));
};
console.log(cmp(d1, d2));
// => false
console.log(cmp(d1, d1));
// => true
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#14.3.1"></script>
Disclaimer: I'm the author of object-scan
const compareKeys = (obj1, obj2) =>
Object.keys(obj1).length === Object.keys(obj2).length &&
Object.keys(obj1).every((key) => key in obj2);
var object1 = {
"banana": "bar",
"orange": "foo"
};
var object2 = {
"banana": "foo",
"orange": "bar"
};
var object3 = {
"aaaaa": "bar",
"jjjjj": "foo"
};
//Expected output: true
console.log(compareKeys(object1, object2));
//Expected output: false
console.log(compareKeys(object1, object3));
I have tried this approach keep in mind the structure should be same too.
Therefore,
{name: "Anuj", isTaxPayer: true}
{isTaxPayer: true, name: "Shonster"}
will not be same
var keys1 = [];
var keys2 = [];
var json1 = { "parentKey" :
{ "childKey" : { "nestedKey" : "value" },
"childKey2": "value2" }};
var json2 = { "parentKey" :
{ "childKey" : { "nestedKey" : "value2",
"nestedKey2": "value3"},
"childKey2": "value2" }};
function getKeys(Obj, res){
res === undefined ? res = [] : 1;
for(prop in Obj){
res.push(prop);
if(Object.prototype.toString.call(Obj[prop]).match(/\s([a-zA-Z]+)/)[1] === "Object"){
getKeys(Obj[prop], res);
}
}
return res;
}
function isDifferent(){
keys1 = getKeys(json1).join("").toLowerCase();
keys2 = getKeys(json2).join("").toLowerCase();
keys1 === keys2 ? alert("SAME") : alert("NOPE");
}
isDifferent();

javascript return property value from nested array of objects based on condition

i have an array of objects, in which each object could have an array of objects inside.
var mylist = [
{
"email" : null,
"school" : "schoolA",
"courses": [
{
"name" : 'ABC',
"type" : "chemistry"
},
{
"name" : 'XYZ',
"type": "math"
}
]
},
{
"email" : null,
"school": "schoolB"
}
];
i want to return course name if one of the course type is chemistry.
The course types are unique and even if they are some duplicates, we return the first one.
var result = mylist.some(function (el) {
el.courses && el.courses.some(function(u) {
if (u.type === 'chemistry') {
return u.name;
};
})
});
console.log('outcome:', result);
my code is not working at this stage.
The some callback should return a truthy or falsy value, which tells some whether to keep going (true = stop), and some returns a boolean, not a callback return value.
Probably simplest in this case just to assign directly to result:
var result;
mylist.some(function(el) {
return (el.courses || []).some(function(course) {
if (course.type === "chemistry") {
result = course.name;
return true;
}
return false;
});
});
Live Example:
var mylist = [
{
"email" : null,
"school" : "schoolA",
"courses": [
{
"name" : 'ABC',
"type" : "chemistry"
},
{
"name" : 'XYZ',
"type": "math"
}
]
},
{
"email" : null,
"school": "schoolB"
}
];
var result;
mylist.some(function(el) {
return (el.courses || []).some(function(course) {
if (course.type === "chemistry") {
result = course.name;
return true;
}
return false;
});
});
console.log(result);
I stuck to ES5 syntax since you didn't use any ES2015+ in your question, but in ES2015+, simplest probably to use nested for-of loops:
let result;
outer: for (const el of mylist) {
for (const course of el.courses || []) {
if (course.type === "chemistry") {
result = course.name;
break outer;
}
}
}
Live Example:
const mylist = [
{
"email" : null,
"school" : "schoolA",
"courses": [
{
"name" : 'ABC',
"type" : "chemistry"
},
{
"name" : 'XYZ',
"type": "math"
}
]
},
{
"email" : null,
"school": "schoolB"
}
];
let result;
outer: for (const el of mylist) {
for (const course of el.courses || []) {
if (course.type === "chemistry") {
result = course.name;
break outer;
}
}
}
console.log(result);
You could use reduce() method to iterate through each object in array and then find() method to find if some course matches type.
var mylist = [{"email":null,"school":"schoolA","courses":[{"name":"ABC","type":"chemistry"},{"name":"XYZ","type":"math"}]},{"email":null,"school":"schoolB"}]
const course = mylist.reduce((r, {courses}) => {
if (courses && !r) {
const course = courses.find(({type}) => type == 'chemistry');
if (course) r = course.name;
}
return r;
}, null)
console.log(course)

Recursively collect values for property using lodash

For a nested complex object or array, I would like to collect all values for a given property name. Example:
var structure = {
name: 'alpha',
array: [
{ name: 'beta' },
{ name: 'gamma' }
],
object: {
name: 'delta',
array: [
{ name: 'epsilon' }
]
}
};
// expected result: [ 'alpha', 'beta', 'gamma', 'delta', 'epsilon' ]
It's obvious how to achieve this using plain JS, but: Is there any elegant, concise approach using lodash?
[edit] Current variant below. Nicer solutions welcome!
function getPropertyRecursive(obj, property) {
var values = [];
_.each(obj, function(value, key) {
if (key === property) {
values.push(value);
} else if (_.isObject(value)) {
values = values.concat(getPropertyRecursive(value, property));
}
});
return values;
}
This can be done elegantly with the following mixin, which is a recursive version of _.toPairs:
_.mixin({
toPairsDeep: obj => _.flatMap(
_.toPairs(obj), ([k, v]) =>
_.isObjectLike(v) ? _.toPairsDeep(v) : [[k, v]])
});
then to get the result you want:
result = _(structure)
.toPairsDeep()
.map(1)
.value()
If there are scalar properties other than name, you'll have to filter them out:
result = _(structure)
.toPairsDeep()
.filter(([k, v]) => k === 'name')
.map(1)
.value()
There's no Lodash/Underscore function that I know if that will do what you're looking for.
So what are you looking to do? Well, specifically you're looking to extract the values of all of the name properties out of a aggregate structure. How would we generalize that? In other words, if you were looking to add such functionality to Lodash/Underscore, how would you reframe the problem? After all, most people don't want to get the values of the name properties. You could create a generic function where you supply the name of the property you want, but...thinking even more abstractly than that, what you really want to do is visit all of the nodes in a aggregate structure and do something with them. If we consider aggregate structures in JavaScript as generic trees, we can take a recursive approach using a depth-first walk:
function walk(o, f) {
f(o);
if(typeof o !== 'object') return;
if(Array.isArray(o))
return o.forEach(e => walk(e, f));
for(let prop in o) walk(o[prop], f);
}
Now we can do what you're looking for by walking the structure and adding things to an array:
const arr = [];
walk(structure, x => if(x !== undefined && x.name) arr.push(x.name));
This isn't quite functional enough for my tastes, though...there's a side effect on arr here. So an even better generic approach (IMO) would be to allow a context object to ride along (or an accumulator if you will, a la Array#reduce):
function walk(o, f, context) {
f(o, context);
if(typeof o !== 'object') return context;
if(Array.isArray(o)) return o.forEach(e => walk(e, f, context)), context;
for(let prop in o) walk(o[prop], f, context);
return context;
}
Now you can call it like this, side-effect free:
const arr = walk(structure, (x, context) => {
if(x !== undefined && x.name) context.push(x.name);
}, []);
Iterate the object recursively using _.reduce():
function getPropertyRecursive(obj, prop) {
return _.reduce(obj, function(result, value, key) {
if (key === prop) {
result.push(value);
} else if (_.isObjectLike(value)) {
return result.concat(getPropertyRecursive(value, prop));
}
return result;
}, []);
}
var structure = {
name: 'alpha',
array: [{
name: 'beta'
}, {
name: 'gamma'
}],
object: {
name: 'delta',
array: [{
name: 'epsilon'
}]
}
};
var result = getPropertyRecursive(structure, 'name');
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.2/lodash.min.js"></script>
You could iterate the object and call it again for arrays or objects. Then get the wanted property.
'use strict';
function getProperty(object, key) {
function iter(a) {
var item = this ? this[a] : a;
if (this && a === key) {
return result.push(item);
}
if (Array.isArray(item)) {
return item.forEach(iter);
}
if (item !== null && typeof item === 'object') {
return Object.keys(item).forEach(iter, item);
}
}
var result = [];
Object.keys(object).forEach(iter, object);
return result;
}
var structure = { name: 'alpha', array: [{ name: 'beta' }, { name: 'gamma' }], object: { name: 'delta', array: [{ name: 'epsilon' }] } };
console.log(getProperty(structure,'name'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Based on the answer ( https://stackoverflow.com/a/39822193/3443096 ) , here's another idea for mixin:
_.mixin({
extractLeaves: (obj, filter, subnode, subpathKey, rootPath, pathSeparator) => {
var filterKv = _(filter).toPairs().flatMap().value()
var arr = _.isArray(obj) ? obj : [obj]
return _.flatMap(arr, (v, k) => {
if (v[filterKv[0]] === filterKv[1]) {
var vClone = _.clone(v)
delete vClone[subnode]
vClone._absolutePath = rootPath + pathSeparator + vClone[subpathKey]
return vClone
} else {
var newRootPath = rootPath
if (_.isArray(obj)) {
newRootPath = rootPath + pathSeparator + v[subpathKey]
}
return _.extractLeaves(
v[subnode], filter, subnode,
subpathKey, newRootPath, pathSeparator
)
}
})
}
});
This work for this example JSON, where you want to extract leaf-nodes:
{
"name": "raka",
"type": "dir",
"children": [{
"name": "riki",
"type": "dir",
"children": [{
"name": "roko",
"type": "file"
}]
}]
}
Use it this way:
_.extractLeaves(result, {type: "file"}, "children", "name", "/myHome/raka", "/")
And you will get:
[
{
"name": "roko",
"type": "file",
"_absolutePath": "/myHome/raka/riki/roko"
}
]

remove object using filter doesn't work using javascript

What's wrong with my code below?
var arr = [{
"key": 123,
"player_data": {
"id": 1
}
}, {
"key": 456,
"player_data": {
"id": 1
}
}]
arr.filter(function(el) {
return el.key != 123;
});
console.log(arr);
I expect the object with key 123 will be removed?
Update
arr.filter(function (el) {
return el.key != 123;
});
to
arr = arr.filter(function (el) {
return el.key != 123;
});
Note : filter function does not change the existing array rather returns a new array.
For reference - https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
arr.filter() returns a new array and does not change the original. You are logging the original which has not been affected. Try..
var arr = [{
"key": 123,
"player_data": {
"id": 1
}
}, {
"key": 456,
"player_data": {
"id": 1
}
}]
var foo = arr.filter(function (el) {
return el.key !== 123;
});
console.log(foo);

check if a key exists with certain "value" exists in an object using Javascript

{ foo: [ foo1: true ],
bar: [ bar1: true, bar2: true ],
foobar: [ foobar1: true ] }
Here is an object with values as array-like-objects. I want to find whether a key exist with its value as [ bar1: true, bar2: true ]. If a key is associated with the value, then return the key.
I repeat, I am searching the key associated with the value given in an object.
First, that's the right syntax:
var map = {
foo: { foo1: true },
bar: { bar1: true, bar2: true },
foobar: { foobar1: true }
};
Now to look it up use this function:
function findKey(map, term) {
var found = [];
for(var property in map) {
if(map.hasOwnProperty(property)) {
for(var key in map[property]) {
if(map[property].hasOwnProperty(key) && key === term) {
found.push(property);
}
}
}
}
return found;
}
For example:
var results = findKey(map, 'bar1');
console.log(results);
// [ 'bar' ]
You can convert array-like-object to normal array with Array.prototype.slice.call()
function findKey(obj, key) {
for (var prop in obj) {
var arrLikeObj = obj[prop];
var arr = Array.prototype.slice.call(arrLikeObj);
if (arr.indexOf(key) != -1) {
return prop;
}
}
}

Categories