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);
}
Related
So the goal is to have included only those endpoints (and its methods e.g. get, post...) which are defined in the configuration file.
Example structure object that holds all the endpoints.
et swaggerApis = {
header: {
propertyHeader: "valueHeader"
},
blocks: [
{
tags: ["Tenant & User"],
paths: {
"/tenants": {
post: {
property: "value"
},
get: {
property: "value"
}
},
"/tenants/{id}": {
post: {
property: "value"
},
get: {
property: "value"
},
delete: {
property: "value"
}
}
}
}
]
};
Example of the configuration file that holds only those endpoints and its methods we want to have included in the final object.
const CONFIG = {
api: {
include: {
"/tenants/{id}": ["get"]
}
}
};
So far here is my second version of the JavaScript code that works but introduces a high cyclometric complexity and is hard to read. I'm pretty new to JavaScript and looking a way not just to improve this code.
function includeEnpointsByConfig(data) {
for (let blockItem of data.blocks) {
for (let path in blockItem.paths) { //console.log(blockItem.paths[path])
let result = setMethodsOfEndpoint(path, blockItem.paths[path]);
if (result === 'undefined') {
delete blockItem.paths[path] // if the config does not contain, remove
} else {
blockItem.paths[path] = result;
}
}
}
return data;
}
function setMethodsOfEndpoint(path, value) {
let newMethods = {};
for (let confPath in CONFIG.api.include) {
if (path === confPath) { // match endpoint in config and swaggerApis object
if (CONFIG.api.include[confPath].length > 0) { // if array in config is not empty , filter
for (let c of CONFIG.api.include[confPath]) { //console.log(c); // get
for (let v in value) {// properties of object tenants/{id} => {get{}, post{}}
if (v === c) {
newMethods = { ...newMethods, [v]: value[v] };
}
}
}
} else {// if array in config is empty , return param "value" from setMethodsOfEndpoint so we will include all methods of endpoint
return value;
}
} else {
return 'undefined'
}
}
if (Object.keys(newMethods).length !==0) { // if in the config is in the array (nothing that match with swaggerEndpoints e.g. typo get --> gte)
return newMethods
} else {
return value;
}
}
console.log(includeEnpointsByConfig(swaggerApis));
Code can be found also here
https://codesandbox.io/s/blazing-worker-1emzl?file=/src/index2.js
I believe there is a way to do it much easier, cleaner and more effective.
Thank you
With some creative usage of Array.prototype.forEach(), Object.keys() and Object.entries():
swaggerApis.blocks.forEach(block => {
Object.entries(block.paths).forEach(([path, methods]) => {
if (!CONFIG.api.include[path]) {
delete block.paths[path];
} else {
Object.keys(methods).forEach(method => {
if (!CONFIG.api.include[path].includes(method)) {
delete methods[method];
}
});
}
});
});
Complete snippet:
const swaggerApis = {
header: {
propertyHeader: "valueHeader"
},
blocks: [
{
tags: ["Tenant & User"],
paths: {
"/tenants": {
post: {
property: "value"
},
get: {
property: "value"
}
},
"/tenants/{id}": {
post: {
property: "value"
},
get: {
property: "value"
},
delete: {
property: "value"
}
}
}
}
]
};
const CONFIG = {
api: {
include: {
"/tenants/{id}": ["get"]
}
}
};
swaggerApis.blocks.forEach(block => {
Object.entries(block.paths).forEach(([path, methods]) => {
if (!CONFIG.api.include[path]) {
delete block.paths[path];
} else {
Object.keys(methods).forEach(method => {
if (!CONFIG.api.include[path].includes(method)) {
delete methods[method];
}
});
}
});
});
console.log(swaggerApis);
I am having json object like below which will be dynamic,
let data_existing= [
{
"client":[
{
"name":"aaaa",
"filter":{
"name":"123456"
}
}
]
},
{
"server":[
{
"name":"qqqqq",
"filter":{
"name":"984567"
}
}
]
},
]
From the inputs i will get an object like below,
let data_new = {
"client":[
{
"name":"bbbbb",
"filter":{
"name":"456789"
}
}
]
}
I need to append this object into the existing "client" json object. Expected output will be like,
[
{
"client":[
{
"name":"aaaa",
"filter":{
"name":"123456"
}
},
{
"name":"bbbb",
"filter":{
"name":"456789"
}
}
]
},
{
"server":[
{
"name":"qqqqq",
"filter":{
"name":"984567"
}
}
]
}
]
And, if the "data_new" is not exists in the main objects, it should as new objects like below, for example,
let data_new = {
"server2":[
{
"name":"kkkkk",
"filter":{
"name":"111111"
}
}
]
}
output will be like,
[
{
"client":[
{
"name":"aaaa",
"filter":{
"name":"123456"
}
},
]
},
{
"server":[
{
"name":"qqqqq",
"filter":{
"name":"984567"
}
}
]
},
{
"server2":[
{
"name":"kkkkk",
"filter":{
"name":"11111"
}
}
]
}
]
I tried the below method, but it is not working as expected. Some help would be appreciated.
Tried like below and not worked as expected,
function addData(oldData, newData) {
let [key, value] = Object.entries(newData)[0]
return oldData.reduce((op, inp) => {
if (inp.hasOwnProperty(key)) {
console.log("111");
op[key] = inp[key].concat(newData[key]);
} else {
console.log(JSON.stringify(inp));
op = Object.assign(op, inp);
}
return op
}, {})
}
Your function seems to work when the key already belongs to data_existing (e.g.: "client").
But you have to handle the second use-case: when the key was not found in the objects of data_existing (e.g.: "server2").
This shall be performed after the reduce loop, adding the new item to data_existing if the key was not found.
Here is an example of how you could achieve that:
function addData(inputData, inputItem) {
const [newKey, newValue] = Object.entries(inputItem)[0];
let wasFound = false; // true iif the key was found in list
const res = inputData.reduce((accumulator, item) => {
const [key, value] = Object.entries(item)[0];
const keyMatch = key === newKey;
if (keyMatch) {
wasFound = true;
}
// concatenate the lists in case of key matching
const newItem = { [key]: keyMatch ? [...value, ...newValue] : value };
return [...accumulator, newItem];
}, []);
if (!wasFound) {
res.push(inputItem); // if key was not found, add item to the list
}
return res;
}
Hope it helps.
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" ]
I want to list all leaves ids where group name is i.e. "group110"
So, output for this example is 014, 288, 223 and 244.
Here is content of my JSON file:
{
"name": "visualization",
"children": [
{
"name": "group100",
"children": [
{
"name": "group110",
"children": [
{
"name": "group111",
"children": [
{"id":"014","link":"-","name":" Animals/70","decade":"-"}
]
},
{
"name": "group112",
"children": [
{"id":"288","link":"-","name":"Heidelberg platen press","decade":"1960s"}
]
},
{
"name": "group113",
"children": [
{"id":"223","link":"-","name":"Camera Praktica Super TL – shutter release","decade":"1960s"},
{"id":"244","link":"-","name":"Mechanical calculator, Facit","decade":"1950s"}
]
}
]
},
Try this way. Find the group using a recursive method and collect leaf nodes using another recursive method.
function getLeafNodes(leafNodes, obj){
if(obj.children){
obj.children.forEach(function(child){getLeafNodes(leafNodes,child)});
} else{
leafNodes.push(obj);
}
}
function findIds(json,name){
if(json.children){
if(json.name==name) {
var leafNodes = [];
getLeafNodes(leafNodes,json);
console.log(leafNodes.map(function(leafNode){ return leafNode.id; })); //Logs leaf node ids to the console
} else {
json.children.forEach(function(child){
findIds(child,name);
});
}
}
}
Execution of following code will print ["014", "288", "223", "244"]
findIds(actualJSON,"group110");
The following code traverses the tree recursively. If param has children, then its children will be traversed. Otherwise, its id will be appended to the results array, thus, at the end results will contain the id of the leaves. getResults returns results to simplify its usage.
var results = [];
function getResults(param) {
if (!!param.children) {
for (var child in param.children) {
getResults(param.children[child]);
}
} else {
results[results.length] = param.id;
}
return results;
}
Here is a generic terse recursive answer for finding nodes using some jquery ($.map).
Watch out for stack overflows if the data is deep though!
Also it will not continue searching inside a matching node for more matching sub nodes, so it's only applicable if the search term does not nest logically.
This method makes use of the array flattening feature of $.map.
var found = (function walk(obj, searchKey, searchTerm) {
if(!$.isPlainObject(obj)) return null;
return obj[searchKey] === searchTerm ? [obj] : $.map(obj, function (lev) {
return walk(lev, searchKey, searchTerm);
});
})(data, 'name', 'group110');
Expanding on that to solve the specific problem above...
var found = (function walk(obj, searchTerm) {
if(!$.isPlainObject(obj)) return null;
return obj.name == searchTerm
? $.map(obj.children, function(c){
return $.map(c.children, function(f){ return f.id; }); })
: $.map(obj.children, function (lev) {
return walk(lev, searchTerm); });
})(data, 'group110');
Or rather
var found = (function walk(obj, lambda, term) {
if(!($.isPlainObject(obj) || $.isArray(obj))) return null;
return lambda.call(obj, term)
? $.map(obj.children, function(c){
return $.map(c.children, function(f){ return f.id; }); })
: $.map(obj.children, function (lev) {
return walk(lev, searchTerm); });
})(data, function(a){ return this.name == a; }, 'group110');
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);
}