Reputation: 55
I have an complex Object
{
"a": 1,
"b": {"test": {
"b1": 'b1'
}},
"c": {
"d": [{foo: 1}, {foo: 2}, {foo: 3, bar: 1}, {bar: 12}]
},
}
And I have list of keys:
[
"a",
"b.test.b1",
"c.d[].foo"
]
What I want to do - is to pick all values I have keys for. The problem is - I do not sure how to handle arrays ("c.d[].foo"
). I do not know how long the array is and which elements do or do not have foo
The result should be
{
"a": 1,
"b": {"test": {
"b1": 'b1'
}},
"c": {
"d": [{foo: 1}, {foo: 2}, {foo: 3}]
},
}
UPD If someone interested, here is my implementation of this function:
const deepPick = (input, paths) => {
return paths.reduce((result, path) => {
if(path.indexOf('[]') !== -1) {
if(path.match(/\[\]/g).length !== 1) {
throw new Error(`Multiplie [] is not supported!`);
}
const [head, tail] = path.split('[]');
const array = (get(input, head) || []).reduce((result, item) => {
// if tail is an empty string, we have to return the head value;
if(tail === '') {
return get(input, head);
}
const value = get(item, tail);
if(!isNil(value)) {
result.push(set({} , tail, value));
} else {
result.push(undefined);
}
return result;
}, []);
const existingArray = get(result, head);
if((existingArray || []).length > 0) {
existingArray.forEach((_, i) => {
if(!isNil(get(array[i], tail))) {
set(existingArray, `${i}.${tail}`, get(array[i], tail));
}
});
} else if(array.length > 0) {
set(result, head, array);
}
} else {
set(result, path, get(input, path));
}
return result;
}, {});
}
and here a sandbox to play with
Upvotes: 2
Views: 931
Reputation: 3737
I updated this answer to include a special function that I wrote that solves the problem. I haven't tested it against every possible scenario, but I know with 100% certainty that it runs for your cases.
_.mixin({
"pickSpecial": function pickSpecial(obj, key) {
if (!_.includes(key, '[]')) {
return _.pick(obj, key);
}
else {
const keys = _.split(key, /(\[\]\.|\[\])/);
const pickFromArray = _.chain(obj)
.get(_.first(keys))
.map((nextArrayElement) => pickSpecial(nextArrayElement, _.reduce(_.slice(keys, 2), (curr, next) => `${curr}${next}`, '')))
.compact()
.reject((elem) => (_.isObject(elem) || _.isArray(elem)) && _.isEmpty(elem))
.value();
return _.chain(obj)
.pick(_.first(keys))
.set(_.first(keys), pickFromArray)
.value();
}
}
});
const initialData = {
"a": 1,
"b": {"test": {
"b1": 'b1'
}},
"c": {
"d": [{foo: 1}, {foo: 2}, {foo: 3, bar: 1}, {bar: 12}]
},
};
const keys = [
"a",
"b.test.b1",
"c.d[].foo"
];
/* Expected result
{
"a": 1,
"b": {"test": {
"b1": 'b1'
}},
"c": {
"d": [{foo: 1}, {foo: 2}, {foo: 3}]
},
}
*/
const output = _.chain(keys)
.map((key) => _.pickSpecial(initialData, key))
.reduce((obj, next) => _.merge({}, obj, next), {})
.value();
console.log(output);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Upvotes: 0
Reputation: 7770
map-factory
might help to do this task in elegant way. see here for more details: https://www.npmjs.com/package/map-factory
code will looks like this
const mapper = require("map-factory")();
mapper
.map("a")
.map("b.test.b1")
.map("c.d[].foo");
const input = {
a: 1,
b: {
test: {
b1: "b1"
}
},
c: {
d: [{ foo: 1 }, { foo: 2 }, { foo: 3, bar: 1 }, { bar: 12 }]
}
};
console.log(JSON.stringify(mapper.execute(input)));
Upvotes: 3
Reputation: 23515
Loadash alternative
Idk about loadash, but I would simply remove the []
from your string keys and use a simple function to retrieve what you are looking for.
const obj = {
a: 1,
b: {
test: {
b1: 'b1',
},
},
c: {
d: [{
foo: 1,
}, {
foo: 2,
}, {
foo: 3,
bar: 1,
}, {
bar: 12,
}],
},
};
const myKeys = [
'a',
'b.test.b1',
'c.d[].foo',
].map(x => x.replace(/\[\]/, ''));
function deepSearch(key, obj) {
// We split the keys so we can loop on them
const splittedKeys = key.split('.');
return splittedKeys.reduce((tmp, x, xi) => {
if (tmp === void 0) {
return tmp;
}
if (tmp instanceof Array) {
const dataIndex = tmp.findIndex(y => y[x] !== void 0);
// If the data we are looking for doesn't exists
if (dataIndex === -1) {
return void 0;
}
const data = tmp[dataIndex];
const ptr = data[x];
// Remove the data only if it's the last key we were looking for
if (splittedKeys.length === xi + 1) {
delete data[x];
// If the array element we removed the key from is now empty
// remove it
if (Object.keys(data).length === 0) {
tmp.splice(dataIndex, 1);
}
}
return ptr;
}
const ptr = tmp[x];
// Remove the data only if it's the last key we were looking for
if (splittedKeys.length === xi + 1) {
delete tmp[x];
}
return ptr;
}, obj);
}
console.log('Results', myKeys.map(x => deepSearch(x, obj)));
console.log('Final object', obj);
Upvotes: 0