Is there a way to use _.omit on nested object properties?
I want this to happen:
schema = {
firstName: {
type: String
},
secret: {
type: String,
optional: true,
private: true
}
};
schema = _.nestedOmit(schema, 'private');
console.log(schema);
// Should Log
// {
// firstName: {
// type: String
// },
// secret: {
// type: String,
// optional: true
// }
// }
_.nestedOmit obviously doesn't exist and just _.omit doesn't affect nested properties, but it should be clear what I'm looking for.
It also doesn't have to be underscore, but in my experience it often just makes things shorter and clearer.
You could create a nestedOmit mixin that would traverse the object to remove the unwanted key. Something like
_.mixin({
nestedOmit: function(obj, iteratee, context) {
// basic _.omit on the current object
var r = _.omit(obj, iteratee, context);
//transform the children objects
_.each(r, function(val, key) {
if (typeof(val) === "object")
r[key] = _.nestedOmit(val, iteratee, context);
});
return r;
}
});
and a demo http://jsfiddle.net/nikoshr/fez3eyw8/1/
Detailed solution of this issue is posted in another thread. Please have a look at the below thread
Link - Cleaning Unwanted Fields From GraphQL Responses
Related
I want to descend an object in Javascript looking for a specific string. Unfortunately, this object is built in such a way that it'd be impossible to simply use the source and Ctrl-F for that string, and it's also built in such a way that recursive functions trying to descend it risk getting trapped inside of it forever.
Basically, this object contains itself. Not just once, but in very many areas. I cannot simply say "exclude these keys", as the object is obfuscated and therefore we'd be here all day listing keys, and once we were done we wouldn't have looked at all the data.
As well, I need to be able to descend __proto__ and prototype, as useful strings are hidden in there too. (But only for functions and objects.)
While I'd prefer something along the lines of findStuff(object, /string/ig), that may be hard, so any function that simply has areas clearly marked that the control flow falls to once it's found specific objects (function, string, etc.)
Thank you, and sorry for such a pain in the butt question.
Edit: In case it helps, I'm trying to traverse a compiled Construct2 runtime object. I'm not going to post the full thing here as it's not going to fit in any pastebin no matter how forgiving, and also I don't want to accidentally post resources I don't have the permission to provide. (Don't worry though, I'm not trying to pirate it myself, I'm simply trying to figure out some user-facing functionality)
You could use a WeakSet to keep track of the objects that were already traversed:
function traverseOnce(obj, cb) {
const visited = new WeakSet();
(function traverse(obj) {
for(const [key, value] of Object.entries(obj)) {
if(typeof value === "object" && value !== null) {
if(visited.has(value)) continue;
visited.add(value);
cb(value);
traverse(value);
}
}
})(obj);
}
Through the WeakSet you got O(1) lookup time, and are also sure that this will never leak.
Usable as:
const nested = { other: { a: 1 } };
nested.self = nested;
traverseOnce(nested, console.log);
// nested: { other, self }
// other: { a: 1 }
You could also use a Symbol to flag traversed objects, for that replace new WeakSet() with Symbol(), visited.has(value) with value[visited] and visuted.add(value) with value[visited] = true;
Any time you're traversing a potentially cyclical object, keeping a memo of already traversed objects and breaking if you've seen the current object before is a standard technique. You can use Set to do so.
Keep a list of objects you have recursed into, and then check each new object against that list.
const data = {
foo: {
bar: 1
},
one: 1,
jaz: {
hello: {
x: 1
}
}
};
data.bar = data.foo;
data.foo.foo = data.foo;
data.jaz.hello.foo = data;
function search_for_1() {
const seen = [];
search(data);
function search(object) {
Object.values(object).forEach(value => {
if (typeof value === "object") {
if (seen.includes(value)) {
console.log("Seen this already");
} else {
seen.push(value);
search(value);
}
} else {
if (value === 1) {
console.log("Found 1");
}
}
});
}
}
search_for_1();
Don't reinvent the wheel There are libraries for this kind of stuff.
We use object-scan for all our data processing. It's very powerful once you wrap your head around it. Here is how it would work for your questions
// const objectScan = require('object-scan');
const traverse = (data) => objectScan(['**'], {
filterFn: ({ key, value, parent }) => {
// do something here
},
breakFn: ({ isCircular }) => isCircular === true
})(data);
const circular = { name: 'Max', age: 5, sex: undefined, details: { color: 'black', breed: undefined } };
circular.sex = circular;
circular.details.breed = circular;
console.log(traverse(circular));
/* =>
[ [ 'details', 'breed' ],
[ 'details', 'color' ],
[ 'details' ],
[ 'sex' ],
[ 'age' ],
[ 'name' ] ]
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
I have an Immutable.js Map that looks something like this:
item1: {
prop1: {
potato: true,
turnip: false,
ragamuffin: true
}
},
item2: {
prop1: {
petunia: true,
azalea: false,
stinkweed: true
}
}
I am writing a function that'll take a keyName and newValue. Instead of enumerating each keypath, I'd like to be able to do something like:
state = state.setIn(state.getKeyPath(keyName), newValue)
For this project, duplicate key names are not a concern (i.e., no Map I parse will have >1 instance of any keyName)
ETA: my current solution is OK for now b/c my state will never be nested >1 level deep, but would rather use a built-in method if one exists:
let keyName = 'petunia'
let keyPath
// make a List of all top-level keys, then loop through each
List(sampleMap.keys()).forEach((topLevelKey) => {
// if we find our key, assign our keyPath accordingly
if (sampleMap.hasIn([topLevelKey, keyName]) === true) {
keyPath = [topLevelKey, keyName]
})
})
My mongoose Schema + validation
var schemaInterest = new schema({
active_elements: {
type: [String]
},
pending_elements: {
type:[String]
}
});
schemaInterest.methods.matchElements = function matchElements() {
this.find({active_elements: this.pending_elements}, function(){
//shows all the matched elements
});
};
I don't know how to work with error handling in mongoose yet. I want it so that if the elements match an error will be return if there is no match then the validation is successful. Any ideas?
Try adding the other property in your validation by using this.pending_elements and comparing the arrays using the lodash library's _.isEqual() and _.sortBy() methods:
var schemaInterest = new schema({
active_elements: {
type: [String]
},
pending_elements: {
type: [String]
}
});
schemaInterest.path('active_elements').validate(function (v) {
return _.isEqual(_.sortBy(v), _.sortBy(this.pending_elements))
}, 'my error type');
-- UPDATE --
From the OP comments (thanks to #JohnnyHK for pointing that out), at least one matching element, not the whole array is required thus you would need the _.intersection() method which creates an array of unique values that are included in all of the provided arrays using SameValueZero for equality comparisons:
_.intersection(v, this.pending_elements)
would suffice. Thus your validation function would look like this:
schemaInterest.path('active_elements').validate(function (v) {
return _.intersection(v, this.pending_elements).length > 0
}, 'my error type');
If I have the following object:
var record = {
title: "Hello",
children: [
{
title: "hello",
active: true
},
{
title: "bye",
active: false
}
};
I want to use underscore to determine if one of the children within the record has or does not have a title equal to a variable that will come from a form post, but also needs to be case insensitive... So for example:
var child = { title: "heLLo", active: true }
And underscore ( and this is wrong, and what I need help with ):
if ( _.contains(record.children, child.title) ) {
// it already exists...
} else {
// ok we can add this to the object
}
So basically I don't understand how to do this with underscore when dealing with array objects that have multiple key/value pairs. Also what is the best method for ignoring case? Should this be done in the underscore _.contains function? Regex? Use toLowerCase() beforehand to create the variables? If someone types in any variation of "Hello", "HELLO", "heLLO", etc. I don't want the insert to take place.
Thank you!
Use _.find and RegExp with "i" case-ignore flag
var valueFromPost = "bye";
var someOfChildrenHasValueFromPost = _.find(record.children,function(child){
return child.title.match(new RegExp(valueFromPost,"i"));
});
Update
Here is an example #JSFiddle
JS code:
record = {
children:[
{title:'bye'},
{title:'Bye'},
{title:'Hello'}
]
}
var testValue = function(value) {
return _.find(record.children,function(child){
return child.title.match(new RegExp(value,"i"));
});
}
console.debug(testValue('Bye')); //returns object with "Bye" title
console.debug(testValue('What'));//returns undefined
console.debug(testValue('bye')); //returns object with "bye" title
When a user registers with my API they are returned a user object. Before returning the object I remove the hashed password and salt properties. I have to use
user.salt = undefined;
user.pass = undefined;
Because when I try
delete user.salt;
delete user.pass;
the object properties still exist and are returned.
Why is that?
To use delete you would need to convert the model document into a plain JavaScript object by calling toObject so that you can freely manipulate it:
user = user.toObject();
delete user.salt;
delete user.pass;
Non-configurable properties cannot be re-configured or deleted.
You should use strict mode so you get in-your-face errors instead of silent failures:
(function() {
"use strict";
var o = {};
Object.defineProperty(o, "key", {
value: "value",
configurable: false,
writable: true,
enumerable: true
});
delete o.key;
})()
// TypeError: Cannot delete property 'key' of #<Object>
Another solution aside from calling toObject is to access the _doc directly from the mongoose object and use ES6 spread operator to remove unwanted properties as such:
user = { ...user._doc, salt: undefined, pass: undefined }
Rather than converting to a JavaScript object with toObject(), it might be more ideal to instead choose which properties you want to exclude via the Query.prototype.select() function.
For example, if your User schema looked something like this:
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
},
name: {
type: String,
required: true
},
pass: {
type: String,
required: true
},
salt: {
type: String,
required: true
}
});
module.exports = {
User: mongoose.model("user", userSchema)
};
Then if you wanted to exclude the pass and salt properties in a response containing an array of all users, you could do so by specifically choosing which properties to ignore by prepending a minus sign before the property name:
users.get("/", async (req, res) => {
try {
const result = await User
.find({})
.select("-pass -salt");
return res
.status(200)
.send(result);
}
catch (error) {
console.error(error);
}
});
Alternatively, if you have more properties to exclude than include, you can specifically choose which properties to add instead of which properties to remove:
const result = await User
.find({})
.select("email name");
The delete operation could be used on javascript objects only. Mongoose models are not javascript objects. So convert it into a javascript object and delete the property.
The code should look like this:
const modelJsObject = model.toObject();
delete modlelJsObject.property;
But that causes problems while saving the object. So what I did was just to set the property value to undefined.
model.property = undefined;
Old question, but I'm throwing my 2-cents into the fray....
You question has already been answered correctly by others, this is just a demo of how I worked around it.
I used Object.entries() + Array.reduce() to solve it. Here's my take:
// define dis-allowed keys and values
const disAllowedKeys = ['_id','__v','password'];
const disAllowedValues = [null, undefined, ''];
// our object, maybe a Mongoose model, or some API response
const someObject = {
_id: 132456789,
password: '$1$O3JMY.Tw$AdLnLjQ/5jXF9.MTp3gHv/',
name: 'John Edward',
age: 29,
favoriteFood: null
};
// use reduce to create a new object with everything EXCEPT our dis-allowed keys and values!
const withOnlyGoodValues = Object.entries(someObject).reduce((ourNewObject, pair) => {
const key = pair[0];
const value = pair[1];
if (
disAllowedKeys.includes(key) === false &&
disAllowedValues.includes(value) === false
){
ourNewObject[key] = value;
}
return ourNewObject;
}, {});
// what we get back...
// {
// name: 'John Edward',
// age: 29
// }
// do something with the new object!
server.sendToClient(withOnlyGoodValues);
This can be cleaned up more once you understand how it works, especially with some fancy ES6 syntax. I intentionally tried to make it extra-readable, for the sake of the demo.
Read docs on how Object.entries() works: MDN - Object.entries()
Read docs on how Array.reduce() works: MDN - Array.reduce()
I use this little function just before i return the user object.
Of course i have to remember to add the new key i wish to remove but it works well for me
const protect = (o) => {
const removes = ['__v', '_id', 'salt', 'password', 'hash'];
m = o.toObject();
removes.forEach(element => {
try{
delete m[element]
}
catch(O_o){}
});
return m
}
and i use it as I said, just before i return the user.
return res.json({ success: true, user: await protect(user) });
Alternativly, it could be more dynamic when used this way:
const protect = (o, removes) => {
m = o.toObject();
removes.forEach(element => {
try{
delete m[element]
}
catch(O_o){}
});
return m
}
return res.json({ success: true, user: await protect(user, ['salt','hash']) });