admin管理员组文章数量:1429829
I've been racking my brain over this one for awhile and thought maybe it's time to post here.
I have a plex object structure that can have a nested item propery any level deep. Here is an example:
{
resourceType: 'QuestionnaireResponse',
item: [
{
linkId: 'Floors',
answer: []
},
{
linkId: 'KID',
answer: [
{
valueBoolean: false
}
]
},
{
linkId: 'Age',
answer: [
{
valueString: '≥30 Years'
}
]
},
{
linkId: 'UnicornGroup',
item: [
{
linkId: 'DoYouLikeUnicorns',
answer: [{valueBoolean: true}]
},
{
linkId: 'DoYouLikeFHIR'
}
],
answer: []
}
]
}
I want to end up with an object that looks like this:
{
resourceType: 'QuestionnaireResponse',
item: [
{
linkId: 'KID',
answer: [
{
valueBoolean: false
}
]
},
{
linkId: 'Age',
answer: [
{
valueString: '≥30 Years'
}
]
},
{
linkId: 'UnicornGroup',
item: [
{
linkId: 'DoYouLikeUnicorns',
answer: [{valueBoolean: true}]
}
]
}
]
}
That is I want to filter out item objects that have an empty answer array and don't have a nested object that has a non empty answer array.
This is what I have but it's not working:
var res = fItems.filter(function f(o) {
if (o.answer && o.answer.length > 0) {
return true
} else {
if(o.item){
return f(o.item);
}
}
});
I created a REPL Here. We're using ramda in our project so if the solution uses ramda thats fine too. Thanks for the time.
I've been racking my brain over this one for awhile and thought maybe it's time to post here.
I have a plex object structure that can have a nested item propery any level deep. Here is an example:
{
resourceType: 'QuestionnaireResponse',
item: [
{
linkId: 'Floors',
answer: []
},
{
linkId: 'KID',
answer: [
{
valueBoolean: false
}
]
},
{
linkId: 'Age',
answer: [
{
valueString: '≥30 Years'
}
]
},
{
linkId: 'UnicornGroup',
item: [
{
linkId: 'DoYouLikeUnicorns',
answer: [{valueBoolean: true}]
},
{
linkId: 'DoYouLikeFHIR'
}
],
answer: []
}
]
}
I want to end up with an object that looks like this:
{
resourceType: 'QuestionnaireResponse',
item: [
{
linkId: 'KID',
answer: [
{
valueBoolean: false
}
]
},
{
linkId: 'Age',
answer: [
{
valueString: '≥30 Years'
}
]
},
{
linkId: 'UnicornGroup',
item: [
{
linkId: 'DoYouLikeUnicorns',
answer: [{valueBoolean: true}]
}
]
}
]
}
That is I want to filter out item objects that have an empty answer array and don't have a nested object that has a non empty answer array.
This is what I have but it's not working:
var res = fItems.filter(function f(o) {
if (o.answer && o.answer.length > 0) {
return true
} else {
if(o.item){
return f(o.item);
}
}
});
I created a REPL Here. We're using ramda in our project so if the solution uses ramda thats fine too. Thanks for the time.
Share Improve this question edited Oct 17, 2018 at 21:58 cobolstinks asked Oct 17, 2018 at 21:45 cobolstinkscobolstinks 7,15118 gold badges73 silver badges104 bronze badges 2-
Is the empty
answerGroup
array property in the Unicorn Group supposed to be there, and if so, is it supposed to be removed from the result? I supposed equivalently, can you have bothitem
andanswer
on a node? – Scott Sauyet Commented Oct 17, 2018 at 23:28 - Hi no I don't want the empty answer array included in the unicornGroup. But I do want the unicornGroup added to the result (just like second json object is) – cobolstinks Commented Oct 18, 2018 at 1:16
4 Answers
Reset to default 2I think filter()
is actually the wrong tool for this because it can't handle the situation easily where you want to recursively filter the item
array. To do that you need to set the items
property to a new filtered array and you end up mutating your original. Maybe a better direction is to just build up a new array by adding the the items you want rather than filtering. The case is simple in the items that are not groups with a child items array — you can just add those if they have answers. The items, however, have to be handled differently. Maybe something like this will help:
let obj = {resourceType: 'QuestionnaireResponse',item: [{linkId: 'Floors',answer: []},{linkId: 'KID',answer: [{valueBoolean: false}]},{linkId: 'Age',answer: [{valueString: '≥30 Years'}]},{linkId: 'UnicornGroup',item: [{linkId: 'DoYouLikeUnicorns',answer: [{valueBoolean: true}]},{linkId: 'DoYouLikeFHIR'}],answer: []}]}
function filterAnswers(item_arr){
return item_arr.reduce((arr, current) => {
// deal with groups
if (current.item && current.item.length){
let item = filterAnswers(current.item)
if (item.length) {
let ret_obj = {linkId: current.linkId, item:item}
arr.push(ret_obj)
}
}
// deal with the simple case
else if(current.answer && current.answer.length)
arr.push(current)
return arr
}, [])
}
let filtered_items = filterAnswers(obj.item)
console.log(filtered_items)
To keep the code simple I am pretending (maybe) that the answers
property on the pound groups is always empty. It's not clear from the example if these items might have answers
and empty item
array or both item
and answer
. Either way it's just a matter of testing and adding it to the object before pushing.
Here's one possibility:
const filterAnswers = ({item = [], ...rest}) => {
const items = item.map(filterAnswers).filter(
node => (node.answer && node.answer.length)
|| (node.item && node.item.length)
)
return Object.assign({...rest}, items.length ? {item: items} : {})
}
const allItems = {"item": [{"answer": [], "linkId": "Floors"}, {"answer": [{"valueBoolean": false}], "linkId": "KID"}, {"answer": [{"valueString": "≥30 Years"}], "linkId": "Age"}, {"answer": [], "item": [{"answer": [{"valueBoolean": true}], "linkId": "DoYouLikeUnicorns"}, {"linkId": "DoYouLikeFHIR"}], "linkId": "UnicornGroup"}], "resourceType": "QuestionnaireResponse"}
console.log(filterAnswers(allItems))
While Ramda (disclaimer: I'm a Ramda author) might help at the edges (filter(either(path(['answer', 'length']), path(['item', 'length'])))
for instance), this sort of problem would not easily be made point-free, I believe, and I don't think Ramda would add a lot.
I think I have it via a recursive solution. The idea is to go down to the deepest level and then ... note this filter:
items.filter(i => i.answer || (i.item && i.item.length > 0));
The first condition is straightforward, but we need to consider the second in case after pruning the lower items we have an item array that is non empty for an object (even if its own answer array is empty).
let allItems = {
resourceType: 'QuestionnaireResponse',
item: [{
linkId: 'Floors',
answer: []
},
{
linkId: 'KID',
answer: [{
valueBoolean: false
}]
},
{
linkId: 'Age',
answer: [{
valueString: '≥30 Years'
}]
},
{
linkId: 'UnicornGroup',
item: [{
linkId: 'DoYouLikeUnicorns',
answer: [{
valueBoolean: true
}]
},
{
linkId: 'DoYouLikeFHIR'
}
],
answer: []
},
{
linkId: 'DBZGroup', // this object should plete go away too because all of its children will be removed
item: [{
linkId: 'DoYouLikeUnicorns',
answer: []
},
{
linkId: 'DoYouLikeFHIR'
}
],
answer: []
}
]
}
function filter(items) {
items.forEach(i => {
if (i.item && i.item.length > 0) i.item = filter(i.item);
if (i.answer && i.answer.length === 0) delete i.answer;
});
return items.filter(i => i.answer || (i.item && i.item.length > 0));
}
// make a deep copy if you don't want to mutate the original
allItems.item = filter(allItems.item);
console.log(allItems);
For an example of utilising a number of Ramda functions to solve this:
const fn = R.pipe(
R.evolve({
item: R.chain(R.cond([
[nonEmptyProp('item'), R.o(R.of, x => fn(x))],
[nonEmptyProp('answer'), R.of],
[R.T, R.always([])]
]))
}),
R.when(R.propEq('answer', []), R.dissoc('answer'))
)
The main points being:
R.evolve
can be used to map over specific keys of an object, in this case to update theitems
array.R.chain
can be used to both map over a list and remove elements, by return the item wrapped as a single element array (here usingR.of
) or an empty array respectively.- For non-empty
item
properties, we recursively call the function and wrap it in an array - For non-empty
answer
properties, we include the item by wrapping it in an array - For everything else, we return an empty array to exclude it
- Finally, we remove the
answer
property for any items that had nested items along with an emptyanswer
value.
See your full example below.
const nonEmptyProp = R.propSatisfies(R.plement(R.either(R.isNil, R.isEmpty)))
const fn = R.pipe(
R.evolve({
item: R.chain(R.cond([
[nonEmptyProp('item'), R.o(R.of, x => fn(x))],
[nonEmptyProp('answer'), R.of],
[R.T, R.always([])]
]))
}),
R.when(R.propEq('answer', []), R.dissoc('answer'))
)
////
const data = {
resourceType: 'QuestionnaireResponse',
item: [
{
linkId: 'Floors',
answer: []
},
{
linkId: 'KID',
answer: [
{
valueBoolean: false
}
]
},
{
linkId: 'Age',
answer: [
{
valueString: '≥30 Years'
}
]
},
{
linkId: 'UnicornGroup',
item: [
{
linkId: 'DoYouLikeUnicorns',
answer: [{valueBoolean: true}]
},
{
linkId: 'DoYouLikeFHIR'
}
],
answer: []
}
]
}
const expected = {
resourceType: 'QuestionnaireResponse',
item: [
{
linkId: 'KID',
answer: [
{
valueBoolean: false
}
]
},
{
linkId: 'Age',
answer: [
{
valueString: '≥30 Years'
}
]
},
{
linkId: 'UnicornGroup',
item: [
{
linkId: 'DoYouLikeUnicorns',
answer: [{valueBoolean: true}]
}
]
}
]
}
console.log(
R.equals(expected, fn(data))
)
<script src="//cdnjs.cloudflare./ajax/libs/ramda/0.25.0/ramda.min.js"></script>
本文标签: recursively filter complex object in javascriptStack Overflow
版权声明:本文标题:recursively filter complex object in javascript - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1745555075a2663136.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论