admin管理员组文章数量:1201199
This is a question similar to How to remove undefined and null values from an object using lodash?. However, the solutions proposed there do not conserve the constructor. In addition to that, I want to only delete those keys which starts, say with '_'.
Here is what I am looking for, and can't seem to manage to get from lodash :
Input : new Cons({key1 : 'value1', key2 : {key21 : 'value21', _key22: undefined}, key3: undefined, _key4 : undefined})
Output :
{key1 : 'value1', key2 : {key21 : 'value21'}, key3: undefined}
where for example function Cons(obj){_.extend(this, obj)}
I have a solution with omitBy
using lodash, however, I loose the constructor information (i.e. I cannot use instanceof Cons
anymore to discriminate the object constructor). forIn
looked like a good candidate for the recursive traversal but it only provides me with the value
and the key
. I also need the path in order to delete the object (with unset
).
Please note that:
- the object is any valid javascript object
- the constructor is any javascript valid constructor, and the object comes with the constructor already set.
- the resulting object must have
instanceof whatevertheconstructorwas
still true
Is there a better solution (with lodash or else)?
This is a question similar to How to remove undefined and null values from an object using lodash?. However, the solutions proposed there do not conserve the constructor. In addition to that, I want to only delete those keys which starts, say with '_'.
Here is what I am looking for, and can't seem to manage to get from lodash :
Input : new Cons({key1 : 'value1', key2 : {key21 : 'value21', _key22: undefined}, key3: undefined, _key4 : undefined})
Output :
{key1 : 'value1', key2 : {key21 : 'value21'}, key3: undefined}
where for example function Cons(obj){_.extend(this, obj)}
I have a solution with omitBy
using lodash, however, I loose the constructor information (i.e. I cannot use instanceof Cons
anymore to discriminate the object constructor). forIn
looked like a good candidate for the recursive traversal but it only provides me with the value
and the key
. I also need the path in order to delete the object (with unset
).
Please note that:
- the object is any valid javascript object
- the constructor is any javascript valid constructor, and the object comes with the constructor already set.
- the resulting object must have
instanceof whatevertheconstructorwas
still true
Is there a better solution (with lodash or else)?
Share Improve this question edited May 23, 2017 at 11:53 CommunityBot 11 silver badge asked May 16, 2016 at 4:35 user3743222user3743222 18.7k5 gold badges73 silver badges75 bronze badges 2 |5 Answers
Reset to default 16You can create a function that recursively omits keys through the use of omitBy() and mapValues() as an assisting function for traversing keys recursively. Also note that this also supports array traversal for objects with nested arrays or top level arrays with nested objects.
function omitByRecursively(value, iteratee) {
var cb = v => omitByRecursively(v, iteratee);
return _.isObject(value)
? _.isArray(value)
? _.map(value, cb)
: _(value).omitBy(iteratee).mapValues(cb).value()
: value;
}
function Cons(obj) {
_.extend(this, omitByRecursively(obj, (v, k) => k[0] === '_'));
}
Example:
function omitByRecursively(value, iteratee) {
var cb = v => omitByRecursively(v, iteratee);
return _.isObject(value)
? _.isArray(value)
? _.map(value, cb)
: _(value).omitBy(iteratee).mapValues(cb).value()
: value;
}
function Cons(obj) {
_.extend(this, omitByRecursively(obj, (v, k) => k[0] === '_'));
}
var result = new Cons({
key1 : 'value1',
key2 : {
key21 : 'value21',
_key22: undefined
},
key3: undefined,
_key4 : undefined,
key5: [
{
_key: 'value xx',
key7: 'value zz',
_key8: 'value aa'
}
]
});
console.log(result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.12.0/lodash.js"></script>
Update
You can mutate the object itself by creating a function that recursively traverses the object with each() and settles the removal by unset().
function omitByRecursivelyInPlace(value, iteratee) {
_.each(value, (v, k) => {
if(iteratee(v, k)) {
_.unset(value, k);
} else if(_.isObject(v)) {
omitByRecursivelyInPlace(v, iteratee);
}
});
return value;
}
function Cons(obj){_.extend(this, obj)}
var result = omitByRecursivelyInPlace(instance, (v, k) => k[0] === '_');
function omitByRecursivelyInPlace(value, iteratee) {
_.each(value, (v, k) => {
if(iteratee(v, k)) {
_.unset(value, k);
} else if(_.isObject(v)) {
omitByRecursivelyInPlace(v, iteratee);
}
});
return value;
}
function Cons(obj){_.extend(this, obj)}
var instance = new Cons({
key1 : 'value1',
key2 : {
key21 : 'value21',
_key22: undefined
},
key3: undefined,
_key4 : undefined,
key5: [
{
_key: 'value xx',
key7: 'value zz',
_key8: 'value aa'
}
]
});
var result = omitByRecursivelyInPlace(instance, (v, k) => k[0] === '_');
console.log(result instanceof Cons);
console.log(result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.12.0/lodash.js"></script>
You can use the rundef
package.
By default, it will replace all top-level properties with values set to undefined
. However it supports the following options:
mutate
- set this tofalse
to return the same object that you have provided; this will ensure the constructor is not changedrecursive
- set this totrue
to recursively process your object
Therefore, for your use case, you can run:
rundef(object, false, true)
You can use JSON.stringify()
, JSON.parse()
, RegExp.prototype.test()
JSON.parse(JSON.stringify(obj, function(a, b) {
if (!/^_/.test(a) && b === undefined) {
return null
}
return /^_/.test(a) && !b ? void 0 : b
}).replace(/null/g, '"undefined"'));
var obj = {key1 : 'value1', key2 : {key21 : 'value21', _key22: undefined}, key3: undefined, _key4 : undefined}
var res = JSON.stringify(obj, function(a, b) {
if (!/^_/.test(a) && b === undefined) {
return null
}
return /^_/.test(a) && !b ? void 0 : b
}).replace(/null/g, '"undefined"');
document.querySelector("pre").textContent = res;
res = JSON.parse(res);
console.log(res)
<pre></pre>
Disclaimer: I have no idea what lodash supports as built-in functions, but this is very easy to implement with vanilla javascript.
Start with a generic function for filtering your object's keys
// filter obj using function f
// this works just like Array.prototype.filter, except on objects
// f receives (value, key, obj) for each object key
// if f returns true, the key:value appears in the result
// if f returns false, the key:value is skipped
const filterObject = f=> obj=>
Object.keys(obj).reduce((res,k)=>
f(obj[k], k, obj) ? Object.assign(res, {[k]: obj[k]}) : res
, {});
Then a function that filters based on your specific behavior
// filter out keys starting with `_` that have null or undefined values
const filterBadKeys = filterObject((v,k)=> /^[^_]/.test(k) || v !== null && v !== undefined);
Then call it on an object
filterBadKeys({a: null, _b: null, _c: undefined, z: 1});
//=> { a: null, z: 1 }
This can be easily integrated into your constructor now
function Cons(obj) {
_.extend(this, filterBadKeys(obj));
// ...
}
EDIT:
On second thought, instead of butchering a perfectly good function with implicit deep recursion, you could abstract out the generic operations and define a specific "deep" filtering function
const reduceObject = f=> init=> obj=>
Object.keys(obj).reduce((res,k)=> f(res, obj[k], k, obj), init);
// shallow filter
const filterObject = f=>
reduceObject ((res, v, k, obj)=> f(v, k, obj) ? Object.assign(res, {[k]: v}) : res) ({});
// deep filter
const deepFilterObject = f=>
reduceObject ((res, v, k, obj)=> {
if (f(v, k, obj))
if (v && v.constructor === Object)
return Object.assign(res, {[k]: deepFilterObject (f) (v)});
else
return Object.assign(res, {[k]: v});
else
return res;
}) ({});
const filterBadKeys = deepFilterObject((v,k)=> /^[^_]/.test(k) || v !== null && v !== undefined);
filterBadKeys({a: null, _b: null, _c: undefined, _d: { e: 1, _f: null }, z: 2});
//=> { a: null, _d: { e: 1 }, z: 2 }
Integration into your constructor stays the same
function Cons(obj) {
_.extend(this, filterBadKeys(obj));
// ...
}
@ryeballar's answer mostly worked, except I wanted three additional features:
- first recurse, then do the
iteratee
check, since after recursing on the object it is possible that it should be omitted - have it work with arrays
- some typing
Also took some ideas from: https://codereview.stackexchange.com/a/58279
export function omitByRecursivelyInPlace<T extends object | null | undefined>(value: T, iteratee: (v: any, k: string) => any) {
_.each(value, (v, k) => {
// no longer an if/else
if (_.isObject(v)) {
omitByRecursivelyInPlace(v, iteratee);
}
if (iteratee(v, k)) {
// check for array types
if (_.isArray(value)) _.pullAt(value, [parseInt(k)]);
else _.unset(value, k);
}
});
return value;
}
Not sure about the performance here. Open to feedback. May not be exactly what the OP is asking for.
See https://github.com/lodash/lodash/issues/723 for discussion around this topic in the official lodash repo. Looks like it won't be supported.
本文标签:
版权声明:本文标题:javascript - How to delete recursively undefined properties from an object - while keeping the constructor chain? - Stack Overfl 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738597846a2101890.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
omitBy
after the Constructor, or could you do it inside the constructor? – Steven Lambert Commented May 16, 2016 at 5:04Cons
is just an example, the constructor is not chosen by me. – user3743222 Commented May 16, 2016 at 6:54