I'd like to transform this array to another array using lodash 2.4.2:
authorities will be mapped in roles. and we don't touch the other properties.
From:
{
"items": [
{
"_id": "admin",
"authorities": [
{
"name": "ROLE_ADMIN"
}
]
},
{
"_id": "user",
"authorities": [
{
"name": "ROLE_USER"
}
]
}
]
}
to this array
{
"items": [
{
"_id": "admin",
"roles": [
"ROLE_ADMIN"
]
},
{
"_id": "user",
"role": [
"ROLE_USER"
]
}
]
}
can you help me please?
As charlietfl said, in the future posts you must provide examples of code, that you've written to solve your problem.
Here's the solution, it's pretty simple:
const initialObject = {
"items": [
{
"_id": "admin",
"authorities": [
{
"name": "ROLE_ADMIN"
}
]
},
{
"_id": "user",
"authorities": [
{
"name": "ROLE_USER"
}
]
}
]
}
const processedObject = _
.map(initialObject.items, item => {
return {
_id: item._id,
roles: _.map(item.authorities, a => a.name)
}
})
It is just a re-mapping of the key name. For performance reasons I would try to avoid the use of delete. You can see the performance differences in this benchmark, only use 'delete' if the object gets persisted later on.
for (var item of items) {
item.roles = item.authorities
// possible but slow
// delete item.authorities
item.authorities = undefined
}
Edit: #vanelizarov code is even better if you want to avoid mutation of the object itself and clean properties. but it is also slower.
vanelizarov code: x 5,025,370 ops/sec ±1.30% (90 runs sampled)
my code: x 31,583,811 ops/sec ±0.95% (92 runs sampled)
Related
I have tried to find a solution for my problem in the last two hours, which included trying myself, scanning lodash docs as well as SO for any suitable answer, but did not come up with anything remotely working or practical. I would be very grateful for help.
I have an object that can be any depth.
e.g.
{
"name": "alpha",
"children": [
{
"name": "beta",
"children": [
{
"name": "gamma",
"children": [
{
"name": "delta",
"children": []
}
]
}
]
},
{
"name": "epsilon",
"children": [
{
"name": "zeta",
"children": [
{
"name": "eta",
"children": []
}
]
}
]
}
]
}
I am looking for a function which will return the whole branch of this object where there is a matching name (if possible without lodash but if really needed its ok).
Given an input of 'gamma' I expect it to return
{
"name": "alpha",
"children": [
{
"name": "beta",
"children": [
{
"name": "gamma",
"children": [
{
"name": "delta",
"children": []
}
]
}
]
}
]
}
Given an input 't' I expect it to return the whole object, since it is included in the names of children in both branches.
You can separate this problem into two parts, first let's test if the name is present in the tree:
function hasStr(item, str) {
return item.name.includes(str) || item.children.some(x => hasStr(x, str));
}
hasStr(item, 'gamma'); // true
You also asked to have a function that returns the passed object and return only a filtered version of the children:
function possibleAnswer(item, str) {
return {
name: item.name,
children: item.children.filter(x => hasStr(x, str)),
};
}
possibleAnswer(item, 'gamma'); // will return desired object
This problem can be solved with recursion as the depth is not know.
getNames = (nameChild, match) => {
if (nameChild.length < 1) return false;
if (nameChild.name.includes(match)) return true;
k = nameChild.children
.filter((child) =>
getNames(child, match))
return (k.length > 0) ? k : false;
}
suppose the object is assign to nameObj variable then
nameObj.children = getNames(nameObj, 'zeta');
this will do the work!
I have a multilevel nested document (its dynamic and some levels can be missing but maximum 3 levels). I want to update all the children and subchildren routes if any. The scenario is same as in any Windows explorer, where all subfolders' route need to change when a parent folder route is changed. For eg. In the below example, If I am at route=="l1/l2a" and it's name needs to be edited to "l2c", then I will update it's route as route="l1/l2c and I will update all childrens' route to say "l1/l2c/l3a".
{
"name":"l1",
"route": "l1",
"children":
[
{
"name": "l2a",
"route": "l1/l2a",
"children":
[
{
"name": "l3a",
"route": "l1/l2a/l3a"
}]
},
{
"name": "l2b",
"route": "l1/l2b",
"children":
[
{
"name": "l3b",
"route": "l1/l2b/l3b"
}]
}
]
}
Currently I am able to go to a point and I am able to change its name and ONLY its route in the following manner:
router.put('/navlist',(req,res,next)=>{
newname=req.body.newName //suppose l2c
oldname=req.body.name //suppose l2a
route=req.body.route // existing route is l1/l2a
id=req.body._id
newroute=route.replace(oldname,newname); // l1/l2a has to be changed to l1/l2c
let segments = route.split('/');
let query = { route: segments[0]};
let update, options = {};
let updatePath = "";
options.arrayFilters = [];
for(let i = 0; i < segments.length -1; i++){
updatePath += `children.$[child${i}].`;
options.arrayFilters.push({ [`child${i}.route`]: segments.slice(0, i + 2).join('/') });
} //this is basically for the nested children
updateName=updatePath+'name'
updateRoute=updatePath+'route';
update = { $setOnInsert: { [updateName]:newDisplayName,[updateRoute]:newroute } };
NavItems.updateOne(query,update, options)
})
The problem is I am not able to edit the routes of it's children if any i.e it's subfolder route as l1/l2c/l3a. Although I tried using the $[] operator as follows.
updateChild = updatePath+'.children.$[].route'
updateChild2 = updatePath+'.children.$[].children.$[].route'
//update = { $set: { [updateChild]:'abc',[updateChild2]:'abc' } };
Its important that levels are customizable and thus I don't know whether there is "l3A" or not. Like there can be "l3A" but there may not be "l3B". But my code simply requires every correct path else it gives an error
code 500 MongoError: The path 'children.1.children' must exist in the document in order to apply array updates.
So the question is how can I apply changes using $set to a path that actually exists and how can I edit the existing route part. If the path exists, it's well and good and if the path does not exist, I am getting the ERROR.
Update
You could simplify updates when you use references.Updates/Inserts are straightforward as you can only the update target level or insert new level without worrying about updating all levels. Let the aggregation takes care of populating all levels and generating route field.
Working example - https://mongoplayground.net/p/TKMsvpkbBMn
Structure
[
{
"_id": 1,
"name": "l1",
"children": [
2,
3
]
},
{
"_id": 2,
"name": "l2a",
"children": [
4
]
},
{
"_id": 3,
"name": "l2b",
"children": [
5
]
},
{
"_id": 4,
"name": "l3a",
"children": []
},
{
"_id": 5,
"name": "l3b",
"children": []
}
]
Insert query
db.collection.insert({"_id": 4, "name": "l3a", "children": []}); // Inserting empty array simplifies aggregation query
Update query
db.collection.update({"_id": 4}, {"$set": "name": "l3c"});
Aggregation
db.collection.aggregate([
{"$match":{"_id":1}},
{"$lookup":{
"from":"collection",
"let":{"name":"$name","children":"$children"},
"pipeline":[
{"$match":{"$expr":{"$in":["$_id","$$children"]}}},
{"$addFields":{"route":{"$concat":["$$name","/","$name"]}}},
{"$lookup":{
"from":"collection",
"let":{"route":"$route","children":"$children"},
"pipeline":[
{"$match":{"$expr":{"$in":["$_id","$$children"]}}},
{"$addFields":{"route":{"$concat":["$$route","/","$name"]}}}
],
"as":"children"
}}
],
"as":"children"
}}
])
Original
You could make route as array type and format before presenting it to user. It will greatly simplify updates for you. You have to break queries into multiple updates when nested levels don’t exist ( ex level 2 update ). May be use transactions to perform multiple updates in atomic way.
Something like
[
{
"_id": 1,
"name": "l1",
"route": "l1",
"children": [
{
"name": "l2a",
"route": [
"l1",
"l2a"
],
"children": [
{
"name": "l3a",
"route": [
"l1",
"l2a",
"l3a"
]
}
]
}
]
}
]
level 1 update
db.collection.update({
"_id": 1
},
{
"$set": {
"name": "m1",
"route": "m1"
},
"$set": {
"children.$[].route.0": "m1",
"children.$[].children.$[].route.0": "m1"
}
})
level 2 update
db.collection.update({
"_id": 1
},
{
"$set": {
"children.$[child].route.1": "m2a",
"children.$[child].name": "m2a"
}
},
{
"arrayFilters":[{"child.name": "l2a" }]
})
db.collection.update({
"_id": 1
},
{
"$set": {
"children.$[child].children.$[].route.1": "m2a"
}
},
{
"arrayFilters":[{"child.name": "l2a"}]
})
level 3 update
db.collection.update({
"_id": 1
},
{
"$set": {
"children.$[].children.$[child].name": "m3a"
"children.$[].children.$[child].route.2": "m3a"
}
},
{
"arrayFilters":[{"child.name": "l3a"}]
})
I don't think its possible with arrayFilted for first level and second level update, but yes its possible only for third level update,
The possible way is you can use update with aggregation pipeline starting from MongoDB 4.2,
I am just suggesting a method, you can simplify more on this and reduce query as per your understanding!
Use $map to iterate the loop of children array and check condition using $cond, and merge objects using $mergeObjects,
let id = req.body._id;
let oldname = req.body.name;
let route = req.body.route;
let newname = req.body.newName;
let segments = route.split('/');
LEVEL 1 UPDATE: Playground
// LEVEL 1: Example Values in variables
// let oldname = "l1";
// let route = "l1";
// let newname = "l4";
if(segments.length === 1) {
let result = await NavItems.updateOne(
{ _id: id },
[{
$set: {
name: newname,
route: newname,
children: {
$map: {
input: "$children",
as: "a2",
in: {
$mergeObjects: [
"$$a2",
{
route: { $concat: [newname, "/", "$$a2.name"] },
children: {
$map: {
input: "$$a2.children",
as: "a3",
in: {
$mergeObjects: [
"$$a3",
{ route: { $concat: [newname, "/", "$$a2.name", "/", "$$a3.name"] } }
]
}
}
}
}
]
}
}
}
}
}]
);
}
LEVEL 2 UPDATE: Playground
// LEVEL 2: Example Values in variables
// let oldname = "l2a";
// let route = "l1/l2a";
// let newname = "l2g";
else if (segments.length === 2) {
let result = await NavItems.updateOne(
{ _id: id },
[{
$set: {
children: {
$map: {
input: "$children",
as: "a2",
in: {
$mergeObjects: [
"$$a2",
{
$cond: [
{ $eq: ["$$a2.name", oldname] },
{
name: newname,
route: { $concat: ["$name", "/", newname] },
children: {
$map: {
input: "$$a2.children",
as: "a3",
in: {
$mergeObjects: [
"$$a3",
{ route: { $concat: ["$name", "/", newname, "/", "$$a3.name"] } }
]
}
}
}
},
{}
]
}
]
}
}
}
}
}]
);
}
LEVEL 3 UPDATE: Playground
// LEVEL 3 Example Values in variables
// let oldname = "l3a";
// let route = "l1/l2a/l3a";
// let newname = "l3g";
else if (segments.length === 3) {
let result = await NavItems.updateOne(
{ _id: id },
[{
$set: {
children: {
$map: {
input: "$children",
as: "a2",
in: {
$mergeObjects: [
"$$a2",
{
$cond: [
{ $eq: ["$$a2.name", segments[1]] },
{
children: {
$map: {
input: "$$a2.children",
as: "a3",
in: {
$mergeObjects: [
"$$a3",
{
$cond: [
{ $eq: ["$$a3.name", oldname] },
{
name: newname,
route: { $concat: ["$name", "/", "$$a2.name", "/", newname] }
},
{}
]
}
]
}
}
}
},
{}
]
}
]
}
}
}
}
}]
);
}
Why separate query for each level?
You could do single query but it will update all level's data whenever you just need to update single level data or particular level's data, I know this is lengthy code and queries but i can say this is optimized version for query operation.
you can't do as you want. Because mongo does not support it. I can offer you to fetch needed item from mongo. Update him with your custom recursive function help. And do db.collection.updateOne(_id, { $set: data })
function updateRouteRecursive(item) {
// case when need to stop our recursive function
if (!item.children) {
// do update item route and return modified item
return item;
}
// case what happen when we have children on each children array
}
I have two arrays of object, the first array (printerChart, around 80 elements) is made of the following type of objects:
[{
printerBrand: 'Mutoh',
printerModel: 'VJ 1204G',
headsBrand: 'Epson',
headType: '',
compatibilty: [
'EDX',
'DT8',
'DT8-Pro',
'ECH',
],
},
....
]
The second array (items, around 500 elements) is made of the following type of objects:
[
{
"customData": {
"brand": {
"value": {
"type": "string",
"content": "hp"
},
"key": "brand"
},
"printer": {
"value": {
"type": "string",
"content": "c4280"
},
"key": "printer"
}
},
"name": "DT8 XLXL",
"image": {
"id": "zLaDHrgbarhFSnXAK",
"url": "https://xxxxxxx.net/images/xxxxxx.jpg"
},
"brandId": "xxxxx",
"companyId": "xxxx",
"createdAt": "2018-03-26T14:39:47.326Z",
"updatedAt": "2018-04-09T14:31:38.169Z",
"points": 60,
"id": "dq2Zezwm4nHr8FhEN"
},
...
]
What I want to do is to iterate via the second array and, if the part of the name of an item (i.e. DT8) is included in an element of the array 'compatibility' of the first array, I would like to include a new properties to it from the element of the first array: printerBrand. I have tried but somehow the iteration doesn't take place correctly. This is what I tried:
items.forEach((item) => {
printerChart.forEach((printer) => {
if (printer.compatibilty.some(compatibleElem => (
item.name.includes(compatibleElem)))) {
item.printerBrand = printer.printerBrand;
} else {
item.printerBrand = '';
}
});
});
What am I doing wrong?
You do
items.items.forEach(...)
Shouldn't you be doing
items.forEach(...)
?
I suggest to initialize item.printerBrand with an empty string and use a nested approach of some for getting a brand and to exit the loops, if found.
This prevents to get an empty string even if there is a brand to assign.
items.forEach((item) => {
item.printerBrand = '';
printerChart.some(printer => {
if (printer.compatibilty.some(compatibleElem => item.name.includes(compatibleElem))) {
item.printerBrand = printer.printerBrand;
return true;
}
});
});
So I have a bunch of status codes, in an object (from an API) that returns like this:
{
"location": [
"HOME_ADDRESS_INCOMPLETE",
"HOME_MISSING_ADDRESS"
],
"basics": [
"HOME_MISSING_TYPE"
],
"description": [
"HOME_MISSING_DESCRIPTION"
],
"immersions": [
"AT_LEAST_ONE_STAY_REQUIRED",
"SIMPLE_STAY_MISSING_HOURS",
"SIMPLE_STAY_MISSING_OFFERED_LANGUAGES",
"TANDEM_STAY_MISSING_HOURS",
"TANDEM_STAY_MISSING_OFFERED_LANGUAGES",
"TANDEM_STAY_MISSING_INTERESTED_LANGUAGES",
"TEACHER_STAY_MISSING_HOURLY_PRICE",
"TEACHER_STAY_MISSING_OFFERED_LANGUAGES",
"TEACHER_STAY_MISSING_WEEKLY_PACKAGES"
],
"rooms": [
"NO_ROOMS"
],
"photos": [
"AT_LEAST_ONE_HOME_IMAGE_REQUIRED"
],
"pricing": [
"MISSING_CURRENCY",
"SERVICE_WITHOUT_PRICE",
"DISCOUNT_WITHOUT_PERCENT",
"ROOM_WITHOUT_PRICE"
]
}
The key names, like location correlate to a step in a setup wizard that the user must be placed on depending on what is missing, which is represented by constants like HOME_ADDRESS_INCOMPLETE.
What is the most terse or clear way to start with this object and one constant, like MISSING_CURRENCY, and return the name of the key to which that constant's array belongs to?
Here's what I have so far, but all it does is return the array itself:
const activeStep = Object.values(HomeStatusCodes).filter(statusArray => {
return statusArray.includes(homeActivationResponse.code)
})
Array#find (on the array of keys of that structure, from Object.keys) plus Array#indexOf should do it:
function find(value) {
return Object.keys(data).find(function(key) {
return data[key].indexOf(value) != -1;
});
}
Note that Array#find is new in ES2015, but can readily be shimmed/polyfilled.
Example:
var data = {
"location": [
"HOME_ADDRESS_INCOMPLETE",
"HOME_MISSING_ADDRESS"
],
"basics": [
"HOME_MISSING_TYPE"
],
"description": [
"HOME_MISSING_DESCRIPTION"
],
"immersions": [
"AT_LEAST_ONE_STAY_REQUIRED",
"SIMPLE_STAY_MISSING_HOURS",
"SIMPLE_STAY_MISSING_OFFERED_LANGUAGES",
"TANDEM_STAY_MISSING_HOURS",
"TANDEM_STAY_MISSING_OFFERED_LANGUAGES",
"TANDEM_STAY_MISSING_INTERESTED_LANGUAGES",
"TEACHER_STAY_MISSING_HOURLY_PRICE",
"TEACHER_STAY_MISSING_OFFERED_LANGUAGES",
"TEACHER_STAY_MISSING_WEEKLY_PACKAGES"
],
"rooms": [
"NO_ROOMS"
],
"photos": [
"AT_LEAST_ONE_HOME_IMAGE_REQUIRED"
],
"pricing": [
"MISSING_CURRENCY",
"SERVICE_WITHOUT_PRICE",
"DISCOUNT_WITHOUT_PERCENT",
"ROOM_WITHOUT_PRICE"
]
};
function find(value) {
return Object.keys(data).find(function(key) {
return data[key].indexOf(value) != -1;
});
}
console.log(find("MISSING_CURRENCY"));
Even more terse when you use ES2015 syntax:
const find = value =>
Object.keys(data).find(key => data[key].indexOf(value) != -1);
(Yes, that's really a function.) Here's a live version of that for browsers that support ES2015:
var data = {
"location": [
"HOME_ADDRESS_INCOMPLETE",
"HOME_MISSING_ADDRESS"
],
"basics": [
"HOME_MISSING_TYPE"
],
"description": [
"HOME_MISSING_DESCRIPTION"
],
"immersions": [
"AT_LEAST_ONE_STAY_REQUIRED",
"SIMPLE_STAY_MISSING_HOURS",
"SIMPLE_STAY_MISSING_OFFERED_LANGUAGES",
"TANDEM_STAY_MISSING_HOURS",
"TANDEM_STAY_MISSING_OFFERED_LANGUAGES",
"TANDEM_STAY_MISSING_INTERESTED_LANGUAGES",
"TEACHER_STAY_MISSING_HOURLY_PRICE",
"TEACHER_STAY_MISSING_OFFERED_LANGUAGES",
"TEACHER_STAY_MISSING_WEEKLY_PACKAGES"
],
"rooms": [
"NO_ROOMS"
],
"photos": [
"AT_LEAST_ONE_HOME_IMAGE_REQUIRED"
],
"pricing": [
"MISSING_CURRENCY",
"SERVICE_WITHOUT_PRICE",
"DISCOUNT_WITHOUT_PERCENT",
"ROOM_WITHOUT_PRICE"
]
};
const find = value =>
Object.keys(data).find(key => data[key].indexOf(value) != -1);
console.log(find("MISSING_CURRENCY"));
You could iterate the keys and find the one which property includes the wanted item.
function getKey(object, item) {
return Object.keys(object).find(k => object[k].includes(item));
}
var data = { "location": ["HOME_ADDRESS_INCOMPLETE", "HOME_MISSING_ADDRESS"], "basics": ["HOME_MISSING_TYPE"], "description": ["HOME_MISSING_DESCRIPTION"], "immersions": ["AT_LEAST_ONE_STAY_REQUIRED", "SIMPLE_STAY_MISSING_HOURS", "SIMPLE_STAY_MISSING_OFFERED_LANGUAGES", "TANDEM_STAY_MISSING_HOURS", "TANDEM_STAY_MISSING_OFFERED_LANGUAGES", "TANDEM_STAY_MISSING_INTERESTED_LANGUAGES", "TEACHER_STAY_MISSING_HOURLY_PRICE", "TEACHER_STAY_MISSING_OFFERED_LANGUAGES", "TEACHER_STAY_MISSING_WEEKLY_PACKAGES"], "rooms": ["NO_ROOMS"], "photos": ["AT_LEAST_ONE_HOME_IMAGE_REQUIRED"], "pricing": ["MISSING_CURRENCY", "SERVICE_WITHOUT_PRICE", "DISCOUNT_WITHOUT_PERCENT", "ROOM_WITHOUT_PRICE"] };
console.log(getKey(data, 'MISSING_CURRENCY'));
I'd go for a lookup map:
const stepByStatusCode = new Map()
for (const step in HomeStatusCodes) {
for (const code of HomeStatusCodes[step])
stepByStatusCode.set(code, step);
which you then can use in the most terse way
const activeStep = stepByStatusCode.get(homeActivationResponse.code);
I'm trying to convert this
[ { "_id": "57760ecef5e7478c1e46b892" },
{ "_id": "57760f56f5e7478c1e46b896" },
{ "_id": "57760f7df5e7478c1e46b89a" } ]
to this
"_id" : [ "57760ecef5e7478c1e46b892", "57760f56f5e7478c1e46b896", "57760f7df5e7478c1e46b89a" ]
through code.
I am relatively new to the world of node.js so I don't know what to search for.
You can use the arrow functions of ES6 to do that in a very concise way:
var obj = {_id: yourArray.map((item) => item._id)}
See Arrow functions and map() for details.
var arrr = [{
"_id": "57760ecef5e7478c1e46b892"
}, {
"_id": "57760f56f5e7478c1e46b896"
}, {
"_id": "57760f7df5e7478c1e46b89a"
}]
var newObj = {
"_id": arrr.map(function(elem) {
return elem["_id"];
})
};
console.log(newObj);