I have an array of objects that I would like to turn into an array (or array-like) object where the keys are the unique values for that given property (something like SQL group by).
fiddle:
var obj = [
{
"ClaimId":"111",
"DrugName":"AMBIEN CR",
"PatientId":1571457415
},
{
"ClaimId":"222",
"DrugName":"AMBIEN CR",
"PatientId":1571457415
},
{
"ClaimId":"333",
"DrugName":"LOTREL",
"PatientId":1571457415
},
{
"ClaimId":"444",
"DrugName":"METHYLPREDNISOLONE",
"PatientId":1571457415
},
{
"ClaimId":"555",
"DrugName":"CYMBALTA",
"PatientId":1513895252
},
{
"ClaimId":"666",
"DrugName":"CYMBALTA",
"PatientId":1513895252
},
{
"ClaimId":"777",
"DrugName":"CYMBALTA",
"PatientId":1513895252
},
{
"ClaimId":"888",
"DrugName":"CYMBALTA",
"PatientId":1513895252
},
{
"ClaimId":"147503879TMQ",
"DrugName":"CYMBALTA",
"PatientId":1513895252
},
{
"ClaimId":"999",
"DrugName":"CYMBALTA",
"PatientId":1513895252
}
]
function splitBy(data, prop) {
var returnObj = {};
var returnArray = [];
$.each(data, function (ix, val) {
if (returnObj[val[prop]] === undefined) {
returnObj[val[prop]] = [];
returnObj[val[prop]].push(val);
}
});
console.log(returnObj);
}
splitBy(obj,'PatientId');
In the fiddle you can see that I get the keys of the array like I want (the two unique values in the PatientId property) but I only get the first value. I understand that's because once the key is no longer undefined, then this check isn't ran, but I couldn't figure out quite how to do it and this is as close as I got. How can I do this with one iteration over this collection?
I only get the first value.
That's because you only push the first value - when there had been no key. Change the
if (returnObj[val[prop]] === undefined) {
returnObj[val[prop]] = [];
returnObj[val[prop]].push(val);
}
to
if (returnObj[val[prop]] === undefined) {
returnObj[val[prop]] = [];
}
returnObj[val[prop]].push(val);
jsFiddle Demo
You almost had it, but you forgot the else clause for once the key existed
if (returnObj[val[prop]] === undefined) {
returnObj[val[prop]] = [];
returnObj[val[prop]].push(val);
}else{
returnObj[val[prop]].push(val);
}
Related
I have a keys property that is related to map property. The length of keys correspond with how deep the level of each map property goes. In this case only 2 levels.
If I add another entry to keys then each map property will go one more level deeper.
Below is the data
{
keys: [
"vendorApNbr",
"type"
],
map: {
_default: { <-** 1st level
_default: "'100026'", <-** 2nd level
PT_CC: "'120035'", <-** 2nd level
PT_DC: "'120037'"
},
A-00: { <- ** 1st level
_default: "'120037'" <- ** 2nd level
},
A-01: {
_default: "'120035'"
},
A-02: {
_default: "'120035'"
},
A-03: {
_default: "'120036'"
},
A-04: {
_default: "'100024'"
}
}
}
I would like to create an array of arrays where each item in the array is iteration of going from level 1 to level 2 (but can go down more levels if needed)
i.e.
[
['_default', '_default', "'10026'"],
['_default', 'PT_CC', "'120035'"],
['_default', 'PP_DC', "'120037'"],
['A-00', '_default', "'120037'"],
['A-01', '_default', "'120035'"],
...etc
['A-04', '_default', "'100024'"]
]
I'm limited to ES5 or lodash. I'm thinking of recursion but not sure how to approach this. Any suggestion can help.
Edit
also to have a way to turn the array form back to nested object form
What about this? It doesn't care about how many nested the object is and what the level is. Additionally, each depth could be different.
var obj = {
"_default": {
"_default": "'100026'",
"PT_CC": "'120035'",
"PT_DC": "'120037'"
},
"A-00": {
"_default": "'120037'"
},
"A-01": {
"_default": "'120035'"
},
"A-02": {
"_default": "'120035'"
},
"A-03": {
"_default": "'120036'"
},
"A-04": {
"_default": "'100024'"
}
}
var result = [];
function rec(acc, obj) {
if (typeof obj === "object") {
for (var key in obj) {
rec(acc.concat([key]), obj[key]);
}
return;
}
result.push(acc.concat([obj]));
}
rec([], obj);
console.log(result);
You can do it by using Depth-Fist Search.
The code below is an example extracted from this webpage. The difference here is that is concatenates every key, but you can use the same algorithm with some modifications to get a list.
var obj = {
baz: {
foo: {
bar: "5"
},
hell: {
sin: "0"
}
},
a: {
b: "1"
}
};
var hash = {};
var str = '';
var dfs = function(obj, str) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'string')
hash[str + key] = obj[key];
else {
dfs(obj[key], str + key + '.');
}
}
}
};
dfs(obj, str);
console.log(hash);
You can use the following recursive function to flatten the object's properties in an array.
This function...
only takes one parameter as argument and
doesn't rely on external vars
var data = {
keys: [
"vendorApNbr",
"type"
],
map: {
_default: {
_default: "'100026'",
PT_CC: "'120035'",
PT_DC: "'120037'"
},
A00: {
_default: "'120037'"
},
A01: {
_default: "'120035'"
},
A02: {
_default: "'120035'"
},
A03: {
_default: "'120036'"
},
A04: {
_default: "'100024'"
}
}
};
function makeItFlat(data) {
var newArray = [];
var properties = Object.getOwnPropertyNames(data);
for (var prop of properties) {
if (typeof data[prop] === 'object') {
var flat = makeItFlat(data[prop]);
for (var f of flat) {
if (!Array.isArray(f)) {
f = [f];
}
newArray.push([prop].concat(f));
}
} else {
newArray.push([prop].concat([data[prop]]));
}
}
return newArray;
}
var flatArray = makeItFlat(data.map);
console.log(flatArray);
For converting the array back to the original object, you can use this code:
var flatArray = [
["_default", "_default", "'100026'"],
["_default", "PT_CC", "'120035'"],
["_default", "PT_DC", "'120037'"],
["A00", "_default", "'120037'"],
["A01", "_default", "'120035'"],
["A02", "_default", "'120035'"],
["A03", "_default", "'120036'"],
["A04", "_default", "'100024'"]
];
function convertArrayOfStringsToObject(flatArray) {
var newObject = {};
var key = flatArray[0];
var entry = null;
if (flatArray.length == 2) {
entry = flatArray[1];
} else {
entry = convertArrayOfStringsToObject(flatArray.slice(1));
}
if (key in newObject) {
//key exists already, then merge:
Object.assign(newObject[key], entry);
} else {
newObject[key] = entry;
}
return newObject;
}
function expandArray(flatArray) {
var newObject = {}
for (var line of flatArray) {
var key = line[0];
var entry = convertArrayOfStringsToObject(line.slice(1));
if (key in newObject) {
//key exists already, then merge:
Object.assign(newObject[key], entry);
} else {
newObject[key] = entry;
}
}
return newObject;
}
console.log(expandArray(flatArray));
I have a javascript object structured like this;
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
Given an array of keys ['brand', 'group', 'newGroup', 'newSubGroup'] I want to split the keys into found and missing keys. So for the structure above I should get back;
present = ['brand', 'group']
missing = ['newGroup', 'newSubGroup']
I'm using ES6 and have lodash available, but struggling to find a clean way to produce this.
This is not to just check existence, it's recursively find the keys and return those present and the remaining ones.
Here's a pretty sketchy way that works.
const find = (keys, obj) => {
const string = JSON.stringify(obj);
return keys.reduce(({ present, missing }, key) => {
const match = string.match(new RegExp(`"${key}":`));
if (match) {
present.push(key);
} else {
missing.push(key);
}
return { present, missing };
}, { present: [], missing: [] });
}
You can use this function made for you ;)
var getAttrs = function(obj) {
return [].concat.apply([], Object.keys(obj).map(function (key) {
var results = [key]
if (typeof obj[key] === 'object') {
Array.prototype.push.apply(results, getAttrs(obj[key]))
}
return results
}))
}
It return the list of properties and children properties.
getAttrs({brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}})
> ["brand", "group", "subGroup", "items", "otherSub", "items"]
And you can use it like so:
var lookingFor = ['brand', 'group', 'newGroup', 'newSubGroup']
var existings = getAttrs(obj)
var missings = []
var presents = []
lookingFor.forEach(attr => {
if (existings.indexOf(attr) === -1) {
missings.push(attr)
} else {
presents.push(attr)
}
})
I wrote a function to recursively get unique keys from a nested object, then filtered the array of all the keys you mentioned checking which were present in the result of my function.
var thisObject = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
};
var arr_full = ['brand', 'group', 'newGroup', 'newSubGroup'] ;
var key_array = [];
function addToKeyArray( key_array, object ){
for( var key in object ){
// only get unique keys
if( key_array.indexOf( key ) === -1 ){
key_array.push( key );
}
// concat the result of calling this function recurrsively on object[key]
key_array.concat( addToKeyArray( key_array, object[key] ) );
}
return key_array;
}
var test = addToKeyArray( [], thisObject );
var missing = arr_full.filter( function( el ) {
return test.indexOf( el ) < 0;
});
console.log( test );
console.log( missing )
You can create recursive function using for...in loop inside another function and return object as result..
var obj = {"brand":{"group":{"subGroup":{"items":[]},"otherSub":{"items":[]}}}}
var keys = ['brand', 'group', 'newGroup', 'newSubGroup'] ;
function findKeys(data, keys) {
keys = keys.slice();
function findPresent(data, keys) {
var result = []
for(var i in data) {
if(typeof data[i] == 'object') result.push(...findPresent(data[i], keys))
var index = keys.indexOf(i);
if(index != -1) result.push(...keys.splice(index, 1))
}
return result
}
return {present: findPresent(data, keys), missing: keys}
}
console.log(findKeys(obj, keys))
To keep things clean and readable you can use "for in", inside a nested function for your recursion.
function recur(obj) {
let preMiss = {
present: [],
missing: []
}
let root = traverse => {
for (let key in traverse) {
if (Array.isArray(traverse[key].items)) {
preMiss.missing.push(key);
}
if (typeof traverse[key] === 'object' && !Array.isArray(traverse[key].items)) {
preMiss.present.push(key);
root(traverse[key])
}
}
}
root(obj);
return preMiss;
}
const object = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
console.log(Object.entries(recur(object)));
var toFind = ['brand', 'group', 'newGroup', 'newSubGroup'],
found = [];
var o = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
//called with every property and its value
function process(key,value) {
var i = toFind.indexOf(key);
if(i !== -1){
found.push(key);
toFind.splice(i, 1);
}
}
function traverse(o,func) {
if(!toFind.length) return;
for (var i in o) {
func.apply(this,[i,o[i]]);
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
traverse(o[i],func);
}
}
}
traverse(o,process);
console.log(found); // present
console.log(toFind); // absent
Traverse method taken from https://stackoverflow.com/a/722732/1335165
Even though this question is a bit older, I want to present a rather short solution to the problem.
const recursivelyGetKeys = obj => Object.keys(obj).map(key => typeof obj[key] === 'object'
? [...recursivelyGetKeys(obj[key]), key] : [key]).reduce((p, c) => [...p, ...c], [])
This function will return all keys in the object, so a call to the array arr with
const arr = {
brand: {
group: {
subGroup: {
items: []
},
otherSub: {
items: []
}
}
}
}
will output:
const keys = recursivelyGetKeys(arr) // = ["items", "subGroup", "items", "otherSub", "group", "brand"]
Now to find the intersection set of this and find = ['brand', 'group', 'newGroup', 'newSubGroup'], do:
const found = keys.filter(key => find.some(val === key))
const missing = keys.filter(key => find.every(val !== key))
I'm trying to filter a users JSON via JavaScript's filter, map, and reduce methods. However I cannot get the exact result I pretend.
var users = {
"fooUser": {
"apps": [
{
"id": "7i2j3bk85"
},
{
"id": "o8yasg69h"
}
]
},
"barUser": {
"apps": [
{
"id": "789gbiai7t"
}
]
}};
The logic is: I only know the AppId (and not the User it belogs to), so I'd have to map/filter each User, and return it ONLY if it has that Appid (and return ONLY that AppId).
var filteredApps = Object.keys(users).filter(function (element, index, array) {
var exists = users[element].apps.filter(function (element, index, array) {
if (element.id === 'o8yasg69h') {
return true;
} else {
return false;
}
});
if (exists[0]) {
return true;
} else {
return false;
}
}).map(function (item, index, array) {
return users[item].apps;
});
console.log(filteredApps);
I obtain (a multiArray with no-filtered Apps):
[[
{
id: "7i2j3bk85"
},
{
id: "o8yasg69h"
}
]]
But I would like to obtain (one plain Object, with the filtered App):
{
id: "o8yasg69h"
}
You can do this with the following one-liner:
[].concat(...Object.keys(users).map(x=> users[x].apps)).find(x=> x.id === "o8yasg69h")
To expand it a bit:
[].concat(... // flattens the array of apps
Object.keys(users) // gets the keys of users
.map(x=> users[x].apps) // maps the array to the apps of the user
).find(x=> x.id === "o8yasg69h") // finds app which id is "o8yasg69h"
I'd do it with reduce and ES6 find:
function searchById(id){
return Object.keys(users).reduce(function(result, user){
return result ? result : users[user].apps.find(function(obj){
return obj.id === id;
});
}, false);
}
I am trying to combine an array of objects while removing duplicates based of a particular value, in this case it's id. I want to merge the other properties in each of the objects.
This is what I have:
var myArray = [
{
id : 1,
rendering : 0,
completed : 1
},
{
id : 2,
rendering : 0,
completed : 1
},
{
id : 3,
rendering : 0,
completed : 1
},
{
id : 1,
rendering : 1,
completed : 0
},
{
id : 2,
rendering : 1,
completed : 0
},
{
id : 3,
rendering : 1,
completed : 0
},
]
This is what I want :
var myDesiredArray = [
{
id : 1,
rendering: 1,
completed: 1
},
{
id : 2,
rendering: 1,
completed: 1
},
{
id : 3,
rendering: 1,
completed: 1
},
]
I'd be happy with straight javascript or underscore/lodash. Any suggestions would be greatly appreciated.
Using underscore, here's one way to do it
Define a sum function
function sum(numbers) {
return _.reduce(numbers, function(result, current) {
return result + parseFloat(current);
}, 0);
}
Then groupby over id and pluck needed values and apply sum over them.
_.chain(myArray )
.groupBy("id")
.map(function(value, key) {
return {
id: key,
rendering : sum(_.pluck(value, "rendering")),
completed: sum(_.pluck(value, "completed"))
}
})
.value();
function merge(arr) {
var uniqItems = arr.reduce(function(memo, item) {
if (!memo[item.id]) {
memo[item.id] = item;
} else {
Object.keys(item).forEach(function(k) {
if (item[k]) { memo[item.id][k] = item[k]; }
});
}
return memo;
}, {});
return Object.keys(uniqItems).map(function(k) {
return uniqItems[k];
});
}
merge(myArray); // => myDesiredArray
There is multiple way to do, but as you want to check uniqueness, the easiest way would be to use a temporary map, indexed by your ids, on which you can accumulate the values of the other properties.
When the accumulation is done, convert the Map to a straight array, et voila.
You would do it like so:
var array; // defined elsewhere
var tmp = {}; // temporary map
// accumulate the properties in the map
array.forEach(function(elt) {
if (tmp[elt.id] == null)
tmp[elt.id] = elt
else {
tmp[elt.id].prop += elt.prop;
// (...) do the accumulation here
}
});
// then convert this map to an array by iterating on the ids
Object.keys(tmp).map(function(id) { return tmp[id]; })
Pure JS solution
The first thing you should do is use a map for fast lookup:
var map = {};
myArray.forEach(function(item){
var mapItem = map[item.id] || {
id : item.id,
rendering : 0,
completed : 0
}
mapItem.rendering += item.rendering;
mapItem.completed += item.completed;
map[item.id] = mapItem;
});
Then you can convert the map back to an array:
var myDesiredArray = [];
for (var id in map) {
myDesiredArray.push(map[id]);
}
Here is another vanilla js version using a functional approach:
myArray.reduce(function(prev, cur) {
if (prev[cur.id]) {
prev[cur.id].rendering += cur.rendering;
prev[cur.id].completed += cur.completed;
} else {
prev[cur.id] = cur;
}
return prev;
}, []).filter(function(val) {
return val;
});
Solution with less loop possible
function myMerge(myArray) {
var temp = {},
myDesiredArray = [];
for (var i = 0; i < myArray.length; ++i) {
var elem = myArray[i];
if (!temp[elem.id]) {
temp[elem.id] = {
'id': elem.id,
'rendering': elem.rendering,
'completed': elem.completed,
};
myDesiredArray.push(temp[elem.id])
}
temp[elem.id].rendering += elem.rendering;
temp[elem.id].completed += elem.completed;
}
return myDesiredArray;
}
I have a tree-like structure of a json object
{
"saxena": {
"chewning": {
"betten": {},
"ching": {},
"kelley": {}
},
"kobrinsky": {
"karniely": {},
"naveh": {},
"rozenfeld": {},
"shalom": {}
},
"schriever": {
"brinker": {},
"mcleland": {},
"merrick": {}
},
"vacant": {
"akers": {},
"carlton": {
"marvin": {}
},
"fox": {
"glover": {
"clements": {},
"koya": {}
},
"holden": {}
}
}
},
"bill": {
"phil": {
"bob": {},
"smith": {},
"hello": {}
},
"bye": {
"ok": {},
"hmm": {},
"no": {},
"alright": {}
}
}
}
The root names are saxena and bill. I would like to create a function that can determine the root name of who the user searches for.
For the most simplest case, if they search for saxena, it returns saxena. If they return bill, it returns bill.
For a more complex case, saxena will be returned if the user searches for any of the names under her.
For example, if I search for betten, akers, glovers, or koya, saxena will be returned.
And if I search for bob, smith, or alright, bill will be returned.
This is my work so far. I tried using recursion, but for some reason when I find the selected name, I return an undefined.
var findRootName = function(data, ltmName) {
for (var key in data) {
if (key == ltmName) {
return key;
} else {
findNode(data[key], ltmName);
}
}
}
var findNode = function(data, ltmName) {
for (var key in data) {
if (key == ltmName) {
return key;
} else {
findNode(data[key], ltmName);
}
}
}
http://jsfiddle.net/gthnfta7/7/
Can somebody help me and figure out why my recursive function isn't working?
The problem is that you're not returning anything in the event that the node is found. You can simplify your function by writing it like this:
var findParent = function(data, childName) {
for (var key in data) {
if (key === childName || findParent(data[key], childName)) {
return key;
}
}
};
An alternative technique, if you need to make many calls over the same data, is something like the following:
function makeSearcher(data) {
var paths = (function makePaths(data, parentPath, store) {
var path = parentPath || [];
results = store || {};
Object.keys(data).forEach(function(key) {
var newPaths = path.concat(key);
results[key] = newPaths;
makePaths(data[key], newPaths, results);
});
return results;
})(data);
return function(key) {
var path = paths[key];
return path && path[0];
};
}
var search = makeSearcher(data);
search('clements'); //=> 'savena'
Note that the internal makePaths function is broader than the use here, as it could also be use to return a result like
[ "saxena", "vacant", "fox", "glover", "clements" ]