Get values by property name from an object at different levels - javascript

I have an object in the below format and I need to get all values from the Price property at all levels of the object.
var o = {
Id: 1,
Price: 10,
Attribute: {
Id: 1,
Price: 2,
Modifier: {
Id: 34,
Price: 33
}
}
};
I was thinking of LinqToJS and jquery.map() methods but I'd like to get a method as generic as possible. I tried this but it only works at the first level:
var keys = $.map(o, function(value, key) {
if (key == "Price") {
return value;
}
});

You can use a recursive function which tests the type of name of the property and its type. If it's name is Price, add it to an array. If it's an object, recurse through that object to find a Price key. Try this:
function getPrices(obj, arr) {
$.each(obj, function(k, v) {
if (k == "Price")
arr.push(v);
else if (typeof(v) == 'object')
getPrices(obj[k], arr);
});
return arr;
}
var prices = getPrices(o, []);
console.log(prices); // = [10, 2, 33]
Working example

You can use jQuery's $.map() to do this very easily:
var o = {
Id: 1,
Price: 10,
Attribute: {
Id: 1,
Price: 2,
Modifier: {
Id: 34,
Price: 33
}
}
};
var res = $.map(o, function mapper(obj, key) {
return key === "Price" ? obj : $.map(obj, mapper)
});
document.querySelector("pre").textContent = JSON.stringify(res)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<pre></pre>
This works because of the odd feature jQuery's $.map has where if you return an Array from the callback, it gets flattened into the result.
Therefore we can recursively call $.map with the same function on anything that's not the Price key, and the array it returns will just get emptied into the final result.
You can avoid some calls if you check typeof obj === "object" if you wish.

You could use for..in loop, recursion
var o = {
Id: 1,
Price: 10,
Attribute: {
Id: 1,
Price: 2,
Modifier: {
Id: 34,
Price: 33
}
}
};
var res = [];
(function re(obj) {
for (var prop in obj) {
if (prop === "Price") {
res.push(obj[prop])
} else {
re(obj[prop])
}
}
}(o));
console.log(res)

Related

Getting particular object from nested complex object

I have a below json, want to get object whose id = 111 , depth may vary depending upon the json.
object = [
{
id= 1,
name : 'a',
childNodes : [ {
id=11,
name:'aa',
childNodes:[{
id: 111,
name:'aaaa',
childNodes:[]
}]
}]
}]
required output { id: 111, name:'aaaa', childNodes:[] }
Looking for fastest algorithm or method. Data would be really huge of more then 35000 nodes and depth upto 20.
Any help would be appreciated.
Here is a recursive function using some:
function findNested(arr, id) {
var res;
return arr.some(o => res = Object(o).id === id ? o
: findNested(o.childNodes, id) ) && res;
}
var object = [{
id: 1,
name : 'a',
childNodes : [ {
id: 11,
name:'aa',
childNodes:[{
id: 111,
name:'aaaa',
childNodes:[]
}]
}]
}];
console.log(findNested(object, 111));
console.log(findNested(object, 9));
You can create recursive function for this using for...in loop.
var object = [{"id":1,"name":"a","childNodes":[{"id":11,"name":"aa","childNodes":[{"id":111,"name":"aaaa","childNodes":[]}]}]},{"id":2,"name":"a","childNodes":[{"id":22,"name":"aa","childNodes":[{"id":123,"name":"aaaa","childNodes":[]}]}]}]
function findById(data, id) {
for(var i in data) {
var result;
if(data.id == id) return data
if(typeof data[i] == 'object' && (result = findById(data[i], id))) return result
}
}
console.log(findById(object, 111))
console.log(findById(object, 22))

Return true if value matches any value including nested object value

I have a nested object which consists of :
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
So if I have a string or an array of values that match any of the values including the nested object values in the collection, it will return true. I have tried using _.includes of lodash but I don't know how I can iterate through the nested object.
_.includes(obj.department, 'Operations')
What I am trying to do is more like
_.includes(obj, ['Stephen', 'Operations']) // return true
Use recursion with Array#some to check if the value exists. Array#some returns immediately when the result of the predicate is true.
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
function recursiveIncludes(obj) {
var values = [].slice.call(arguments, 1);
return Object.keys(obj).some(function(key) {
var current = obj[key];
if(values.indexOf(current) !== -1) {
return true;
}
if(typeof current === 'object' && current !== null) {
return recursiveIncludes.apply(null, [current].concat(values));
}
return false;
});
}
console.log('Operations: ', recursiveIncludes(obj, 'Operations'));
console.log('Moses, Stephen: ', recursiveIncludes(obj, 'Moses', 'Stephen'));
console.log('Moses, 58: ', recursiveIncludes(obj, 'Moses', 58));
Here is a recursive approach to it. It extracts all the properties of an object (the leaf properties) into an array. You can then call _.includes() on it.
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
function objToArray(obj) {
var result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result = result.concat(toArray(value));
}
else {
result.push(value);
}
}
return result;
}
_.includes(objToArray(obj), ['Stephen', 'Operations'])
Given an array of strings, this will check and see if the property exists recursively.
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
function hasValues(obj, props)
{
var keys = Object.keys(obj);
for(var i = 0; i < keys.length; i++){
var item = obj[keys[i]];
// If the item is a string or number, do a comparison
if(typeof item === "string" || typeof item === "number"){
var idx = props.indexOf(item);
if(idx >= 0) props.splice(idx, 1);
// If it's an object then search the object recursively
} else if(typeof item === "object"){
hasValues(item, props);
}
}
return props.length === 0;
}
console.log(hasValues(obj, ['Stephen', 'Operations']))
console.log(hasValues(obj, [18, 1]))
console.log(hasValues(obj, [18, '13lkj4']))
You can use flatMap as a mechanism to flatten all the values taken from recursively using map inside the flatMap callback function. After obtaining all the values, we use difference to get the difference between all the values between the object and the values. Lastly, we check the resulting difference if it is empty using isEmpty.
function includesDeep(object, values) {
return _(obj)
.flatMap(function cb(v) { return _.isObject(v)? _.map(v, cb): v; })
.thru(_.partial(_.difference, values))
.isEmpty();
}
var result = includesDeep(obj, ['Stephen', 'Operations']);
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
};
function includesDeep(object, values) {
return _(obj)
.flatMap(function cb(v) { return _.isObject(v)? _.map(v, cb): v; })
.thru(_.partial(_.difference, values))
.isEmpty();
}
var result = includesDeep(obj, ['Stephen', 'Operations']);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
the tests shows that you can't check multi-values & value in-depth with lodash includes,so you must write a function for yourself.
Your solution
describe('includes', () => {
var value = {
foo: 'bar',
fuzz: 'buzz',
value2: {
key: 'value'
}
};
function includes(collection, values) {
return [].concat(values).every((value) => {
return Object.keys(collection).some((key) => {
let it = collection[key];
return typeof it == 'object' ? includes(it, value) : _.includes(it, value);
})
});
}
it('check single value', () => {
expect(includes(value, 'bar')).toBe(true);
expect(includes(value, 'baz')).toBe(false);
});
it('check multi values', () => {
expect(includes(value, ['bar', 'buzz'])).toBe(true);
expect(includes(value, ['baz', 'buzz'])).toBe(false);
});
it('check value in depth', () => {
expect(includes(value, 'value')).toBe(true);
expect(includes(value, 'no-exists')).toBe(false);
});
});
Test
describe('includes', () => {
var value = {
foo: 'bar',
fuzz: 'buzz',
value2: {
key: 'value'
}
};
it('check single value', () => {
expect(_.includes(value, 'bar')).toBe(true);
expect(_.includes(value, 'baz')).toBe(false);
});
it('check multi values', () => {
expect(_.includes(value, ['bar', 'buzz'])).toBe(false);
expect(_.includes(value, ['baz', 'buzz'])).toBe(false);
});
it('check value in depth', () => {
expect(_.includes(value, 'value')).toBe(false);
});
});
function includes(collection, values) {
return [].concat(values).every(function (value) {
return Object.keys(collection).some(function (key) {
var it = collection[key];
return (typeof it == 'object') ? includes(it, value) : _.includes(it, value);
});
});
}
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
};
var tests=[
"Operations",
"Non-Existing Value",
['Stephen', 'Operations'],
['Stephen', 'Non-Existing Value'],
];
tests.forEach(function(test){
console.log("includes(obj,"+JSON.stringify(test)+") => "+ includes(obj,test));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

How to add non duplicate objects in an array in javascript?

I want to add non-duplicate objects into a new array.
var array = [
{
id: 1,
label: 'one'
},
{
id: 1,
label: 'one'
},
{
id: 2,
label: 'two'
}
];
var uniqueProducts = array.filter(function(elem, i, array) {
return array.indexOf(elem) === i;
});
console.log('uniqueProducts', uniqueProducts);
// output: [object, object, object]
live code
I like the class based approach using es6. The example uses lodash's _.isEqual method to determine equality of objects.
var array = [{
id: 1,
label: 'one'
}, {
id: 1,
label: 'one'
}, {
id: 2,
label: 'two'
}];
class UniqueArray extends Array {
constructor(array) {
super();
array.forEach(a => {
if (! this.find(v => _.isEqual(v, a))) this.push(a);
});
}
}
var unique = new UniqueArray(array);
console.log(unique);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.min.js"></script>
Usually, you use an object to keep track of your unique keys. Then, you convert the object to an array of all property values.
It's best to include a unique id-like property that you can use as an identifier. If you don't have one, you need to generate it yourself using JSON.stringify or a custom method. Stringifying your object will have a downside: the order of the keys does not have to be consistent.
You could create an objectsAreEqual method with support for deep comparison, but this will slow your function down immensely.
In two steps:
var array=[{id:1,label:"one"},{id:1,label:"one"},{id:2,label:"two"}];
// Create a string representation of your object
function getHash(obj) {
return Object.keys(obj)
.sort() // Keys don't have to be sorted, do it manually here
.map(function(k) {
return k + "_" + obj[k]; // Prefix key name so {a: 1} != {b: 1}
})
.join("_"); // separate key-value-pairs by a _
}
function getHashBetterSolution(obj) {
return obj.id; // Include unique ID in object and use that
};
// When using `getHashBetterSolution`:
// { '1': { id: '1', label: 'one' }, '2': /*etc.*/ }
var uniquesObj = array.reduce(function(res, cur) {
res[getHash(cur)] = cur;
return res;
}, {});
// Convert back to array by looping over all keys
var uniquesArr = Object.keys(uniquesObj).map(function(k) {
return uniquesObj[k];
});
console.log(uniquesArr);
// To show the hashes
console.log(uniquesObj);
You can use Object.keys() and map() to create key for each object and filter to remove duplicates.
var array = [{
id: 1,
label: 'one'
}, {
id: 1,
label: 'one'
}, {
id: 2,
label: 'two'
}];
var result = array.filter(function(e) {
var key = Object.keys(e).map(k => e[k]).join('|');
if (!this[key]) {
this[key] = true;
return true;
}
}, {});
console.log(result)
You could use a hash table and store the found id.
var array = [{ id: 1, label: 'one' }, { id: 1, label: 'one' }, { id: 2, label: 'two' }],
uniqueProducts = array.filter(function(elem) {
return !this[elem.id] && (this[elem.id] = true);
}, Object.create(null));
console.log('uniqueProducts', uniqueProducts);
Check with all properties
var array = [{ id: 1, label: 'one' }, { id: 1, label: 'one' }, { id: 2, label: 'two' }],
keys = Object.keys(array[0]), // get the keys first in a fixed order
uniqueProducts = array.filter(function(a) {
var key = keys.map(function (k) { return a[k]; }).join('|');
return !this[key] && (this[key] = true);
}, Object.create(null));
console.log('uniqueProducts', uniqueProducts);
You can use reduce to extract out the unique array and the unique ids like this:
var array=[{id:1,label:"one"},{id:1,label:"one"},{id:2,label:"two"}];
var result = array.reduce(function(prev, curr) {
if(prev.ids.indexOf(curr.id) === -1) {
prev.array.push(curr);
prev.ids.push(curr.id);
}
return prev;
}, {array: [], ids: []});
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}
If you don't know the keys, you can do this - create a unique key that would help you identify duplicates - so I did this:
concat the list of keys and values of the objects
Now sort them for the unique key like 1|id|label|one
This handles situations when the object properties are not in order:
var array=[{id:1,label:"one"},{id:1,label:"one"},{id:2,label:"two"}];
var result = array.reduce(function(prev, curr) {
var tracker = Object.keys(curr).concat(Object.keys(curr).map(key => curr[key])).sort().join('|');
if(!prev.tracker[tracker]) {
prev.array.push(curr);
prev.tracker[tracker] = true;
}
return prev;
}, {array: [], tracker: {}});
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}

How to make Javascript map function not to return specific value?

I have this array and variable:
var freqId = 45;
$scope.frequencies = [{Id:124,name:'qqq'},
{Id:589,name:'www'},
{Id:45,name:'eee'},
{Id:567,name:'rrr'}]
I use this row to get all id's from array above:
var inspectionsId = $scope.frequencies.map(function (obj) { return obj.Id; })
The result I get is:
var Id's = [124,589,45,567];
I need to change this row:
$scope.frequencies.map(function (obj) { return obj.Id; })
to retrive all id from frequencies array except where Id equal to freqId variable.
For example desired result is:
var inspectionsId = [124,589,567];
Any idea how can I implemet it?
You can also use Array.prototype.reduce to do both filtering and mapping in a single loop:
var freqId = 45;
$scope = {}; // Dummy scope
$scope.frequencies = [{
Id: 124,
name: 'qqq'
}, {
Id: 589,
name: 'www'
}, {
Id: 45,
name: 'eee'
}, {
Id: 567,
name: 'rrr'
}]
var result = $scope.frequencies.reduce(function(result, current) {
if (current.Id != freqId) {
result.push(current.Id);
}
return result;
}, []);
console.log(JSON.stringify(result));
map is designed to transform data, not filter it. Chain it with filter for the latter.
var freqId = 45;
var input = [{
Id: 124,
name: 'qqq'
}, {
Id: 589,
name: 'www'
}, {
Id: 45,
name: 'eee'
}, {
Id: 567,
name: 'rrr'
}];
var output = input.map(function(obj) {
return obj.Id;
}).filter(function(element) {
return element != freqId
});
console.log(output);
You can use Array.prototype.filter:
var inspectionsId = $scope.frequencies
.map(function(obj) { return obj.Id; })
.filter(function(id) { return id !== 45 })
You'll have seen the filter answers. 99.999% of the time, that's the way to go.
If you have a truly massive array and you think it's important to make just a single pass through it, you could give yourself a function combining map and filter:
// A value to use to mean "leave entry out"
Object.defineProperty(Array.prototype, "OMIT", {
value: {}
});
// The utility function
Object.defineProperty(Array.prototype, "mapFilter", {
value: function(f, thisArg, omissionFlag) {
var result = [];
if (arguments.length < 3) {
omissionFlag = Array.OMIT;
}
Object.keys(this).forEach(function(index) {
var value = f.call(thisArg, this[index], index, this);
if (value !== omissionFlag) {
result.push(value);
}
}, this);
return result;
}
});
// Using it
var freqId = 45;
var input = [{Id: 124, name: 'qqq'}, {Id: 589, name: 'www'}, {Id: 45, name: 'eee'}, {Id: 567, name: 'rrr'}];
var output = input.mapFilter(function(obj) {
return obj.Id == freqId ? Array.OMIT : obj.Id;
});
console.log(output);
This version accepts up to three arguments:
The map/filter function
The value to use as this during callbacks
The value to use to mean "omit this entry," which defaults to Array.OMIT
It calls its callback with the value, index, and array just like forEach and map and such do.
Again, though, I'll emphasize that in the vast, vast majority of cases, filter and then map (or map and then filter if the map makes filtering easier) is the way to go.
That said, a generic "loop with memo" function has broader applicability:
// The utility function
Object.defineProperty(Array.prototype, "memoLoop", {
value: function(memo, f, thisArg) {
Object.keys(this).forEach(function(index) {
f.call(thisArg, memo, this[index], index, this);
}, this);
return memo;
}
});
// Using it
var freqId = 45;
var input = [{Id: 124, name: 'qqq'}, {Id: 589, name: 'www'}, {Id: 45, name: 'eee'}, {Id: 567, name: 'rrr'}];
var output = input.memoLoop([], function(result, obj) {
var id = obj.Id;
if (id != freqId) {
result.push(id);
}
});
console.log(output);
It's a bit like Array#reduce but assumes an unchanging memo value (in our case, the new array), which simplifies the callback somewhat.

Sort multidimensional array of objects with a variable depth in Javascript

So I've recently come across a problem I can't seem to wrap my head around.
Let's say I've defined an array of objects in javascript, and want the user to be able to choose what value to sort that array by.
I have no problem sorting the array when I know the depth, as it would be something along the lines of
array = array.sort(function (a, b) {
return b["foo"]["bar"] - a["foo"]["bar"];
});
but I don't exactly know how to go about doing this when the depth is unknown. I've attempted putting the keys in a string and using eval(), but that does not seem to work.
I've set up a quick example on JSFiddle to better demonstrate what I mean
http://jsfiddle.net/DakotaSv/c35bj02w/2/
If anyone could think of a solution, I'd be grateful!
(Thanks to PatrickD, here is the working JSFiddle for anyone who may find it useful!)
Here is a working solution. It uses ES6-Syntax, but this should not be a problem:
'use strict'
var users = [
{'Name' : 'John', 'Attributes' : {'Age' : 5, 'Height' : 1.5, 'Clothes' : {'Shirts' : 5, 'Pants' : 8}}},
{'Name' : 'Andrew', 'Attributes' : {'Age' : 9, 'Height' : 1.8, 'Clothes' : {'Shirts' : 2, 'Pants' : 5}}},
{'Name' : 'Lucifer', 'Attributes' : {'Age' : 11, 'Height' : 1.3, 'Clothes' : {'Shirts' : 9, 'Pants' : 4}}}
];
function sort(valuePath, array){
let path = valuePath.split('.')
return array.sort((a, b) => {
return getValue(b,path) - getValue(a,path)
});
function getValue(obj, path){
path.forEach(path => obj = obj[path])
return obj;
}
}
console.log(sort('Attributes.Height', users))
console.log(sort('Attributes.Clothes.Shirts', users))
The output is correct.
Maybe this is a solution for variable sorting scheme. The sort attribute is just given, like ['Attributes', 'Height']. This uses the properties Attributes and Height.
It features a temporary storage for faster sorting.
function sort(a, by) {
return a.map(function (el, i) {
return {
index: i,
value: by.reduce(function (obj, property) { return obj[property]; }, el)
};
}).sort(function (a, b) {
return a.value - b.value;
}).map(function (el) {
return a[el.index];
});
}
var users = [{ 'Name': 'John', 'Attributes': { 'Age': 5, 'Height': 1.5, 'Clothes': { 'Shirts': 5, 'Pants': 8 } } }, { 'Name': 'Andrew', 'Attributes': { 'Age': 9, 'Height': 1.8, 'Clothes': { 'Shirts': 2, 'Pants': 5 } } }, { 'Name': 'Lucifer', 'Attributes': { 'Age': 11, 'Height': 1.3, 'Clothes': { 'Shirts': 9, 'Pants': 4 } } }];
document.write('<pre>' + JSON.stringify(sort(users, ['Attributes', 'Height']), 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(sort(users, ['Attributes', 'Clothes', 'Shirts']), 0, 4) + '</pre>');
If I understand your question correctly, you want the user to chose which attribute to sort the array by.
Looking at your fiddle I think that what you need is accessing an attribute specified by the user, fortunately it's possible to specify a variable inside the brackets. Something like:
var obj = {name: 'john'}
var attr = "name";
console.log(obj[attr]); // prints 'john'
Here's your modified fiddle: https://jsfiddle.net/9s5bnfh5/1/
You would need:
a way to represent how to access the sort key given an object
a function that, given a sort key representation and an object, queries the object and produces the key
The manner of representation can be arbitrarily selected, so let's say we decide to encode the access someObject.foo.bar as the string "foo.bar". Then the key producing function would be (adapted from my answer here):
function produceKey(target, path) {
var parts = path.split('.');
while(parts.length) {
var branch = parts.shift();
if (typeof target[branch] === 'undefined') {
return undefined;
}
target = target[branch];
}
return target;
}
which you could then use as:
function produceKey(target, path) {
var parts = path.split('.');
while(parts.length) {
var branch = parts.shift();
if (typeof target[branch] === 'undefined') {
return undefined;
}
target = target[branch];
}
return target;
}
var obj = { foo: { bar: 1, baz: 2 }, arr: [1, 2, 3] };
$(function() {
$("#trigger").click(function() {
$(".result").text(JSON.stringify(produceKey(obj, $("input").val())));
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Targe object:
<pre>
{ foo: { bar: 1, baz: 2 }, arr: [1, 2, 3] }
</pre>
Property path: <input name="path" />
<button id="trigger">Produce value</button>
<div class="result"></div>
The cleanest way of doing this is passing a callback function to your sort algorithm that is executed to retrieve the value to compare on from the object:
function cbSort(toSort, callback)
{
return toSort.sort(function(a, b)
{
return callback(a) - callback(b);
}
}
// usage
var data = [ { x: 1, y: 2 }, {x: 3, y: 1}];
var sorted = cbSort(data, function(item) {
return item.x; // make this as complex as you like
});

Categories

Resources