递归过滤javascript中的复杂对象

时间:2018-10-17 21:45:19

标签: javascript javascript-objects ramda.js

我已经花了很长时间来思考这个问题,并认为也许是时候在这里发布了。

我有一个复杂的对象结构,可以在任何层次上适当地嵌套一个项目。这是一个示例:

 {
    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: []
      }
    ]
  }

我想结束一个看起来像这样的对象:

{
    resourceType: 'QuestionnaireResponse',
    item: [
      {
        linkId: 'KID',
        answer: [
          {
            valueBoolean: false
          }
        ]
      },
      {
        linkId: 'Age',
        answer: [
          {
            valueString: '≥30 Years'
          }
        ]
      },
      {
        linkId: 'UnicornGroup',
        item: [
          {
            linkId: 'DoYouLikeUnicorns',
            answer: [{valueBoolean: true}]
          }
        ]
      }
    ]
  }

那是我想过滤掉具有空答案数组且没有嵌套对象的非空答案数组的项目对象。

这是我所拥有的,但不起作用:

var res = fItems.filter(function f(o) {
  if (o.answer && o.answer.length > 0) { 
    return true 
  } else {
    if(o.item){
      return f(o.item); 
    }
  }
});

我创建了一个REPL Here。我们在项目中使用ramda,因此如果解决方案使用ramda也可以。感谢您的时间。

4 个答案:

答案 0 :(得分:1)

我认为我是通过递归解决方案获得的。想法是深入到最深的层次,然后...注意此过滤器:

items.filter(i => i.answer || (i.item && i.item.length > 0));

第一个条件很简单,但是我们要考虑第二个条件,以免在修剪较低的项目后,我们得到了一个对象数组,该数组对于一个对象来说是非空的(即使它自己的答案数组为空)。

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 complete 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);

答案 1 :(得分:1)

我认为filter()实际上是错误的工具,因为它无法轻松处理要递归过滤item数组的情况。为此,您需要将items属性设置为新的过滤后的数组,然后最终对原始数组进行变异。也许更好的方向是通过添加所需项而不是过滤来构建一个新数组。在不是带有子项数组的组的项中,情况很简单-如果它们有答案,则只需添加即可。但是,这些项目必须以不同的方式处理。也许这样会有所帮助:

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)

为使代码简单,我冒充(也许)复合组的answers属性始终为空。从示例尚不清楚这些项目是否具有answers和空的item数组,或者同时具有itemanswer。无论哪种方式,都只是测试并在推送之前将其添加到对象中。

答案 2 :(得分:1)

这是一种可能性:

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))

尽管Ramda(免责声明:我是Ramda的作者)可能在边缘方面有所帮助(例如filter(either(path(['answer', 'length']), path(['item', 'length'])))),但我认为,这类问题不会轻易变得毫无意义,我不认为认为Ramda会增加很多。

答案 3 :(得分:1)

有关利用许多Ramda函数解决此问题的示例:

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'))
)

要点是:

  • R.evolve可用于映射对象的特定键,在这种情况下,可更新items数组。
  • R.chain既可以用于映射列表,也可以删除元素,方法是分别返回包装为单个元素数组(此处使用R.of)或空数组的项目。
  • 对于非空的item属性,我们以递归方式调用该函数并将其包装在数组中
  • 对于非空answer属性,我们通过将商品包装在数组中来包含商品
  • 对于其他所有内容,我们返回一个空数组以排除它
  • 最后,对于具有嵌套项目以及空answer值的所有项目,我们都将删除answer属性。

请参阅下面的完整示例。

const nonEmptyProp = R.propSatisfies(R.complement(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.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

相关问题