如何将一个对象数组进行合并,以便组合重复对象键处的数据?

3
我正在开发一个React应用程序,模拟一个零售网站。我的主页展示了一个商品,下方有相关产品的卡片组件。当我点击相关产品中的一个按钮时,会打开一个比较对话框,比较当前产品和所点击产品的功能。为了实现这一点,我想创建一个数组,包含所点击产品和主页面产品的结合功能。但是,我一直在努力创建一个对象数组,其中每个独特的功能都有一个关于该功能以及该功能属于哪种产品的数据对象。
目前,我已经能够得到两个产品具有的所有功能的数组,但是如果产品具有重叠的功能,则此数组将重复。这让我不确定如何渲染比较表格,因为我计划映射整个数组并为每个功能创建一行表格。我当前的代码格式化这些功能如下:
formatFeatures: (currentProd, clickedProd) => {
let combinedFeatures = [];
if (clickedProd.features) {
  clickedProd.features.forEach(feature => {
    let obj = {}
    let vals = Object.values(feature);
    obj[vals[0]] = [vals[1], clickedProd.id]
    combinedFeatures.push(obj)
  })
}
currentProd.features.forEach(feature => {
  let obj = {}
  let vals = Object.values(feature);
  obj[vals[0]] = [vals[1], currentProd.id]
  combinedFeatures.push(obj)
})

let formattedFeatures = combinedFeatures.reduce((allFeatures, feature) => {
  if (Object.keys(feature) in allFeatures) {
    allFeatures = [allFeatures[Object.keys(feature)]].concat(feature);
  } else {
    allFeatures.push(feature);
  }
  return allFeatures;
}, [])

这样做的结果是:
[{
  "Fabric": ["100% Cotton", 28214]
}, {
  "Cut": ["Skinny", 28214]
}, {
  "Fabric": ["Canvas", 28212]
}, {
  "Buttons": ["Brass", 28212]
}]

这非常接近我所寻找的内容,我有一个包含产品特征和产品id信息的对象数组,但是“面料”中的重复是我正在努力解决的问题。理想情况下,结果应该看起来像这样:

[{
  "Fabric": ["100% Cotton", 28214],
  ["Canvas", 28212]
}, {
  "Cut": ["Skinny", 28214]
}, {
  "Buttons": ["Brass", 28212]
}]

如果有人能够指导我如何更改格式化函数以实现此目的,我将不胜感激。另外,如果有人知道如何在当前结果下使用单行动态格式化表格的更好方法,请告诉我。
传入我的辅助函数的数据如下:
CurrentProd:
{
  "id": 28212,
  "name": "Camo Onesie",
  "slogan": "Blend in to your crowd",
  "description": "The So Fatigues will wake you up and fit you in. This high energy camo will have you blending in to even the wildest surroundings.",
  "category": "Jackets",
  "default_price": "140.00",
  "created_at": "2021-07-10T17:00:03.509Z",
  "updated_at": "2021-07-10T17:00:03.509Z",
  "features": [{
    "feature": "Fabric",
    "value": "Canvas"
  }, {
    "feature": "Buttons",
    "value": "Brass"
  }]
}

点击产品:

{
  "name": "Morning Joggers",
  "category": "Pants",
  "originalPrice": "40.00",
  "salePrice": null,
  "photo": "https://images.unsplash.com/photo-1552902865-b72c031ac5ea?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80",
  "id": 28214,
  "features": [{
    "feature": "Fabric",
    "value": "100% Cotton"
  }, {
    "feature": "Cut",
    "value": "Skinny"
  }]
}

没有,谢谢大家。 - Callum Reid
3个回答

1

似乎有一个更大的问题是如何组织您的数据。您说理想情况下您的结果应该如下:

[
  {
    "Fabric":
      ["100% Cotton",28214],
      ["Canvas",28212]
  },
  {
    "Cut":
      ["Skinny",28214]
  },
  {
    "Buttons":
      ["Brass",28212]
  }
]

但你真正想要的是每个项目功能的行和相关值的组合列表(如果存在)。然后,你只需要每行要显示的键的数组,并且对象可以让你通过该键访问所需的属性。
键的数组可能如下所示:
["Fabric", "Cut", "Buttons"]

您想要使用这些键访问属性的对象,例如您的CurrentProd,可能是这样的(请注意,您可以通过调用CurrentProd.features["FeatureName"]来访问功能):
{
  "id":28212,
  "name":"Camo Onesie",
// ... //
  "features":  {
    "Fabric": "Canvas",
    "Buttons": "Brass"
  }
}

话虽如此,要获得这些东西,您可以通过对CurrentProd.featuresClickedProd.features的组合数组进行归约来获取键的数组,我们将其称为allFeatureKeys

const allFeatureKeys = [
    ...CurrentProd.features,
    ...ClickedProd.features
  ].reduce((acc, cur) => {
      return acc.findIndex(cur.feature) > -1 ? [...acc, cur.feature] : acc
    },
    []
  );

您可以通过在其特征数组上进行缩减来将CurrentProd修改为上述数据形状,让我们称之为modifiedCurrentProd

const modifiedCurrentProd = {
  ...CurrentProd,
  features: CurrentProd.features.reduce((acc, cur) => {
    return {...acc, [cur.feature]: cur.value} 
  }, {})
}

针对modifiedClickedProd对象重复此操作,然后创建表格值时即可查找到CurrentProd.featuresClickedProd.features的值。

仅作为示例,因为我不知道您的React结构或要显示的数据,您可以通过映射键来渲染表格行,并针对每个功能键,您可以从modifiedCurrentProdmodifiedClickedProd对象的features属性中访问该值:

  <div id="table">
    {allFeatureKeys.map((featureKey) => {
       return <div id="table-row">
         <div>{featureKey}</div>
         <div>
           { 
             modifiedCurrentProd.features[featureKey] !== undefined 
               ? modifiedCurrentProd.id 
               : "n/a"
           }
         </div>
         <div>
           {
             modifiedClickedProd.features[featureKey] !== undefined
               ? modifiedClickedProd.id
               : "n/a"
           }
         </div>
       </div>
     })}
  </div>

1
这确实帮助我改变了解决问题的方式,非常感谢。最终我成功地采用了这种方法。需要注意的是,在allFeatureKeys.reduce()中,我遇到了类型错误,它提示“Fabric不是一个函数”。我认为这是因为三元运算符的返回值被颠倒了。如果找到索引,只有累加器会返回。如果未找到索引,则添加该特征。 - Callum Reid
没问题!我只是一只码农,所以可能会犯错误。reduce函数只是个例子。更重要的是正确设置数据。当然,你也可以编写代码以更高效/适合你的风格,但这并不是重点。 - Chan Youn

1
首先,目标数据结构需要被固定/优化。看起来 OP 专注于基于通用“特征”(如“面料”、“裁剪”、“按钮”)的某些内容,而这些特征值似乎更与“产品”相关联。因此,对于同一“特征”,其值是唯一的,适用于“产品特征”。为了不丢失产品信息,目标格式的“特征项”需要反映其相关产品的“id”属性。
一个可行且仍然足够灵活的目标数据结构可能如下所示...
{
  "Fabric": [{
    productId: 28214,
    value: "100% Cotton",
  }, {
    productId: 28212,
    value: "Canvas",
  }],
  "Cut": [{
    productId: 28214,
    value: "Skinny",
  }],
  "Buttons": [{
    productId: 28212,
    value: "Brass",
  }],
}

任何方法都应该从对产品的特征列表进行数据规范化映射处理开始,其中每个特征项都将被分配其相关产品ID。因此,像“{ feature:“ Buttons”,value:“ Brass”}”这样的特征项暂时映射到“{ productId:28212,feature:“ Buttons”,value:“ Brass”}”中。现在可以连接这两个规范化的数据项列表,并最终处理/减少到最终目标结构…

function mergeBoundProductId(item) {
  return { ...this, ...item };
}
function aggregateProductFeatureValueLists(index, productFeature) {
  const { feature, ...featureValue } = productFeature;
  const featureList = index[feature] ??= [];
  //const featureList = index[feature] || (index[feature] = []);

  featureList.push(featureValue);

  return index;
}

function createIndexOfProductFeatureValues(clickedProd, currentProd) {
  const { features:clickedFeatures } = clickedProd;
  const { features:currentFeatures } = currentProd;

  return [

    ...clickedFeatures.map(mergeBoundProductId, { productId: clickedProd.id }),
    ...currentFeatures.map(mergeBoundProductId, { productId: currentProd.id }),

  ].reduce(aggregateProductFeatureValueLists, {});
}

const currentProduct = {
  id: 28212,
  name: "Camo Onesie",
  // ... more properties ...
  features: [{
    feature: "Fabric",
    value: "Canvas",
  }, {
    feature: "Buttons",
    value: "Brass",
  }],
};
const clickedProduct = {
  name: "Morning Joggers",
  // ... more properties ...
  id: 28214,
  features: [{
    feature: "Fabric",
    value: "100% Cotton",
  }, {
    feature: "Cut",
    value: "Skinny",
  }],
};

console.log(
  'createIndexOfProductFeatureValues(clickedProduct, currentProduct) ...',
  createIndexOfProductFeatureValues(clickedProduct, currentProduct)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

将代码分解为专用进程的优势在于更容易进行重构,例如针对更接近OP所寻找的目标结构的变化。
减少函数的变化很小。只有两个变化,每个变化在其行中几乎不可见...

function mergeBoundProductId(item) {
  return { ...this, ...item };
}
function aggregateProductFeatureValueLists(index, productFeature) {
  const { feature, productId, value } = productFeature;
  const featureList = index[feature] ??= [];

  featureList.push([value, productId]);

  return index;
}

function createIndexOfProductFeatureValues(clickedProd, currentProd) {
  const { features:clickedFeatures } = clickedProd;
  const { features:currentFeatures } = currentProd;

  return [

    ...clickedFeatures.map(mergeBoundProductId, { productId: clickedProd.id }),
    ...currentFeatures.map(mergeBoundProductId, { productId: currentProd.id }),

  ].reduce(aggregateProductFeatureValueLists, {});
}

console.log(
  'createIndexOfProductFeatureValues(clickedProduct, currentProduct) ...',
  createIndexOfProductFeatureValues(clickedProduct, currentProduct)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
  const currentProduct = {
    id: 28212,
    name: "Camo Onesie",
    // ... more properties ...
    features: [{
      feature: "Fabric",
      value: "Canvas",
    }, {
      feature: "Buttons",
      value: "Brass",
    }],
  };
  const clickedProduct = {
    name: "Morning Joggers",
    // ... more properties ...
    id: 28214,
    features: [{
      feature: "Fabric",
      value: "100% Cotton",
    }, {
      feature: "Cut",
      value: "Skinny",
    }],
  };
</script>

最后一个例子的目的也是为了证明易于重构的代码库的优点。
在这里,主要函数从“createIndexOfProductFeatureValues”更名为“createListOfProductFeatureValues”。
它的实现方式也相应地发生了变化,但只是在调用其初始值的 reducer 函数方面有所改变。
reducer 函数的变化也不是很大,只是在处理累积/聚合“collector”对象的方式上有所改变。
结果是一个干净的基于数组的对象结构...

function mergeBoundProductId(item) {
  return { ...this, ...item };
}
function aggregateProductFeatureValueLists(collector, productFeature) {
  const { feature, productId, value } = productFeature;
  const { index, list } = collector;
  const featureItem = index[feature] ??= { feature, values: [] };

  if (featureItem.values.length === 0) {
    list.push(featureItem);
  }
  featureItem.values.push([value, productId]);

  return collector;
}

function createListOfProductFeatureValues(clickedProd, currentProd) {
  const { features:clickedFeatures } = clickedProd;
  const { features:currentFeatures } = currentProd;

  return [

    ...clickedFeatures.map(mergeBoundProductId, { productId: clickedProd.id }),
    ...currentFeatures.map(mergeBoundProductId, { productId: currentProd.id }),

  ].reduce(aggregateProductFeatureValueLists, { index: {}, list: [] }).list;
}

console.log(
  'createListOfProductFeatureValues(clickedProduct, currentProduct) ...',
  createListOfProductFeatureValues(clickedProduct, currentProduct)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
  const currentProduct = {
    id: 28212,
    name: "Camo Onesie",
    // ... more properties ...
    features: [{
      feature: "Fabric",
      value: "Canvas",
    }, {
      feature: "Buttons",
      value: "Brass",
    }],
  };
  const clickedProduct = {
    name: "Morning Joggers",
    // ... more properties ...
    id: 28214,
    features: [{
      feature: "Fabric",
      value: "100% Cotton",
    }, {
      feature: "Cut",
      value: "Skinny",
    }],
  };
</script>


0

你已经循环了一次。你可以不用减少就得到它。

const formatFeatures = (currentProd, clickedProd) => {
  const formattedFeatures = {};

  if (clickedProd.features) {
    clickedProd.features.forEach(feature => {
      const vals = Object.values(feature);

      if (!formattedFeatures.hasOwnProperty(vals[0])) {
        formattedFeatures[vals[0]] = [];
      }
      formattedFeatures[vals[0]].push([vals[1], clickedProd.id]);
    });
  }

  currentProd.features.forEach(feature => {
    const vals = Object.values(feature);

    if (!formattedFeatures.hasOwnProperty(vals[0])) {
      formattedFeatures[vals[0]] = [];
    }
    formattedFeatures[vals[0]].push([vals[1], currentProd.id]);
  })

  return formattedFeatures;
}

const currentProd = {
  "id": 28212,
  "name": "Camo Onesie",
  "slogan": "Blend in to your crowd",
  "description": "The So Fatigues will wake you up and fit you in. This high energy camo will have you blending in to even the wildest surroundings.",
  "category": "Jackets",
  "default_price": "140.00",
  "created_at": "2021-07-10T17:00:03.509Z",
  "updated_at": "2021-07-10T17:00:03.509Z",
  "features": [{
    "feature": "Fabric",
    "value": "Canvas"
  }, {
    "feature": "Buttons",
    "value": "Brass"
  }]
};
const clickedProd = {
  "name": "Morning Joggers",
  "category": "Pants",
  "originalPrice": "40.00",
  "salePrice": null,
  "photo": "https://images.unsplash.com/photo-1552902865-b72c031ac5ea?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80",
  "id": 28214,
  "features": [{
    "feature": "Fabric",
    "value": "100% Cotton"
  }, {
    "feature": "Cut",
    "value": "Skinny"
  }]
};
console.log(formatFeatures(currentProd, clickedProd));
.as-console-wrapper { min-height: 100%!important; top: 0; }


这种方法不会将任何内容推入formattedFeatures基本数组中,但它确实(误)将其用作对象/映射/索引。也许更好的做法是将其初始化为const formattedFeatures = {}; - Peter Seliger
我添加了值,然后在控制台中输出了console.log。 - user6683818
我将累积/聚合的 formattedFeatures 类型从 Array 更改为纯 Object,因为它被用作后者而不是前者。因此,在 SO 中记录现在显示目标结构,而不仅仅是 [] - Peter Seliger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接