【问题标题】:Filter an object based on another object根据另一个对象过滤一个对象
【发布时间】:2020-03-07 07:04:31
【问题描述】:

我想根据另一个对象中的点符号字符串转换一个对象。例如:

const objToTransform = {
  "a": "aValue",
  "b": {
      "c": "cValue",
      "d": "dValue"
  }
};

const filteringObj = {
  "field1": "a",
  "field2": {
    "subfield1": "b.c",
    "subfield2": "b.d"
  }
};

const filteredObj = myFunc(objToTransform, filteringObj);
// expect outputs to be: 
//  {
//   "field1": "aValue",
//   "field2": {
//      "subfield1": "cValue",
//      "subfield2": "dValue"
//    }
//  }

我已经为这个看似简单的事情工作了几个小时,但我仍然无法让它发挥作用。我找到了this topic,它向您展示了如何使用点表示法字符串获取嵌套对象值,但遗憾的是无法进一步获取。

提前感谢您的帮助!

【问题讨论】:

  • 请说明你取得了多远;你可以稍微休息一下,浪费人们的时间,这可能需要 5 分钟,对吧?
  • 我刚刚添加了一个使用 lodash 递归过滤的函数(

标签: javascript object


【解决方案1】:

您可以使用递归在以下步骤中做到这一点:

  • 复制filteringObj
  • 创建辅助函数getByPath,它从嵌套对象获取对象和路径并在该路径返回值
  • 创建一个函数transform,它循环遍历filteringObj的键(我在函数中将其命名为pattern
  • 在循环中检查值是否为对象,然后在该子对象上递归调用 transform 函数
  • 如果不是对象,则使用getByPath函数从原始对象objectToTransform获取值

const objToTransform = {
  "a": "aValue",
  "b": {
      "c": "cValue",
      "d": "dValue"
  }
};

const filteringObj = {
  "field1": "a",
  "field2": {
    "subfield1": "b.c",
    "subfield2": "b.d"
  }
};

function getByPath(obj, path){
  //console.log(path)
  return path.split('.').reduce((ac,a) => ac[a], obj);
}

function transform(obj, pattern){
  for(let key in pattern){
    if(typeof pattern[key] === "object"){
      transform(obj, pattern[key]);
    }
    else{
      pattern[key] = getByPath(obj, pattern[key]);
    }
  }
}
const copy = JSON.parse(JSON.stringify(filteringObj))
transform(objToTransform, copy);
console.log(copy)

【讨论】:

    【解决方案2】:

    您可以使用reduce 方法创建递归函数,该方法将在每次值为对象类型时创建递归调用。

    const objToTransform = {
      "a": "aValue",
      "b": {
        "c": "cValue",
        "d": "dValue"
      }
    };
    
    const filteringObj = {
      "field1": "a",
      "field2": {
        "subfield1": "b.c",
        "subfield2": "b.d"
      }
    };
    
    function transform(a, b) {
      return Object.entries(b).reduce((r, [k, v]) => {
        r[k] = typeof v !== 'object' ?
          v.split('.').reduce((r, e) => r[e], a) :
          transform(a, v)
    
        return r;
      }, {})
    }
    
    const result = transform(objToTransform, filteringObj)
    console.log(result)

    【讨论】:

      【解决方案3】:

      Lodash 允许您非常轻松地使用点表示法。 这正是您想要的(

      import _ from "lodash";
      
      const objToTransform = {
          a: "aValue",
          b: {
              c: "cValue",
              d: "dValue"
          }
      };
      
      const filteringObj = {
          field1: "a",
          field2: {
              subfield1: "b.c",
              subfield2: "b.d"
          }
      };
      
      const filter = (model, data, filtered = {}) => {
          for (const field in model) {
              filtered[field] =
                  typeof model[field] === "object"
                      ? filter(model[field], data, model[field])
                      : _.get(objToTransform, model[field]);
          }
          return filtered;
      };
      
      console.log(filter(filteringObj, objToTransform))
      

      【讨论】:

        【解决方案4】:

        如果您正在使用(或有兴趣使用)Ramda(免责声明:我是它的主要作者之一),这可能对您有用。 Ramda 没有deepMap 函数,但是很容易写一个。使用它,您的转换函数是单行的:

        const deepMap = (fn) => (obj) => 
          is (Object, obj) ? map (deepMap (fn)) (obj) : fn (obj)
        
        const transform = compose(applySpec, deepMap (compose (path, split('.'))))
        
        const objToTransform = {a: "aValue", b: {c: "cValue", d: "dValue"}}
        const filteringObj = {field1: "a", field2: {subfield1: "b.c", subfield2: "b.d"}}
        
        console .log (transform (filteringObj) (objToTransform))
        <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
        <script> const {is, map, compose, applySpec, path, split } = R       </script>

        这里的关键点是applySpec,您可以直接使用filteringObj 稍有不同的格式。这将产生您想要的结果:

        applySpec ({
          field1: path (["a"]), 
          field2: {
            subfield1: path (["b", "c"]), 
            subfield2: path (["b", "d"])
          }
        }) (objToTransform)
        

        transform 函数的其余部分只是将filteringObj 转换为上述形式。

        如果您想继续按照问题中的要求调用它,即transform (objToTransform, filteringObj) 而不是trasform (filteringObj) (objToTransform),它只是稍微复杂一点:

        const transform = (objToTransform, filteringObj) => 
          applySpec(deepMap (compose (path, split('.'))) (filteringObj)) (objToTransform)
        

        您可以编写自己的 Ramda 函数版本,它是 mostly easy。但是适用于嵌套属性的applySpec 版本会有点困难。

        【讨论】:

          【解决方案5】:

          我做了一个递归算法:

          const getRefVal=(ePath, eData)=>ePath.split('.').reduce((r,x)=>r[x], eData);
          
          function myFunc(oData, oModel) {
            let ret = {};
            setKeyVal(oModel,ret);
          
            function setKeyVal(src,res) {              // recursive function
              for (let [k,v] of Object.entries(src)) {
                if (typeof src[k]==='object') { res[k] = {}; setKeyVal(v,res[k]) } 
                else                          { res[k] = getRefVal(v,oData) }
              }
            }
            return ret
          }
          
          const objToTransform= { a: 'aValue', b: { c: 'cValue', d: 'dValue' } }
            ,   filteringObj =  { field1: 'a', field2: { subfield1: 'b.c', subfield2: 'b.d' } };
          
          // test 
          const filteredObj = myFunc(objToTransform, filteringObj);
          // proof
          console.log( filteredObj )

          【讨论】:

            猜你喜欢
            • 2023-03-12
            • 2019-09-30
            • 2020-03-14
            • 2019-09-15
            • 2021-10-24
            • 2022-11-30
            • 2021-10-30
            • 2022-09-27
            • 2020-08-15
            相关资源
            最近更新 更多