如何在检查要过滤的项目是否满足某些条件的同时过滤和映射嵌套的数组项目?

How to filter and map nested array items while checking whether the to be filtered items meet certain criteria?

我需要过滤和映射数组项和每个项的子数组项,同时检查要过滤的项是否具有满足特定条件的特定属性。

用于过滤和映射嵌套数组项的基于数组方法的好的方法是什么?

必需属性

"productName", "productCategory", "content", "id"

并且如果 productImages 中的 status 不是 LinkedExisting

示例数据

[
  {
    "productName": null,
    "productCategory": null,
    "productImages": [
      {
        "content": "1",
        "id": null,
        "data": null,
        "file": null,
        "url": null,
        "status": "Existing"
      },
      {
        "content": "",
        "id": "234",
        "data": "test data",
        "file": "test file",
        "url": null,
        "status": "Existing"
      }
    ]
  },
  {
    "productName": null,
    "productCategory": "hello category",
    "productImages": [
      {
        "content": "1",
        "id": null,
        "data": null,
        "file": null,
        "url": null,
          "status": "Existing"
      },
      {
        "content": "",
        "id": "234",
        "data": "test data",
        "file": "test file",
        "url": null,
          "status": "Existing"
      }
    ]
  },
  {
    "productName": "test product",
    "productCategory": "test category",
    "productImages": [
      {
        "content": "1",
        "id": "123",
        "data": "test data",
        "file": "test file",
        "url": null,
        "status": "Linked"
      },
      {
        "content": "2",
        "id": "234",
        "data": "test data",
        "file": "test file",
        "url": "test",
        "status": "Linked"
      }
    ]
  },
  {
    "productName": "new product",
    "productCategory": "new category",
    "productImages": [
      {
        "content": "2",
        "id": "32332",
        "data": "test data",
        "file": "test file",
        "url": "test",
        "status": "new"
      }
    ]
  }
]

预期输出

[
  {
    "productIndex": 3,
    "name": "new product",
    "category": "new category",
    "filteredImages": [
      {
        "newcontent": "2",
        "id": "32332",
      },
    ]
  },
]

代码

const filteredProducts = products.filter((element) => element.productName && element.productCategory);

你可以用一个 for-of 循环来完成:

const incomplete_items = [];
const new_data = []
for (const [index, item] of data.entries()) {
  if(item.productName && item.productCategory && item.productImages.every((image) => image.id && image.content && !['Existing', 'Linked'].includes(image.status))){
    new_data.push({
      productIndex: index,
      name: item.productName,
      category: item.productCategory,
      filteredImages: item.productImages.map((image) => ({ newcontent: image.content, id: image.id,  }))
    })
  } else {
    incomplete_items.push(index);
  }
}

工作示例

const data = [
  {
    "productName": null,
    "productCategory": null,
    "productImages": [
      { "content": "1",  "id": null, "data": null, "file": null, "url": null, "status": "Existing" },
      { "content": "","id": "234","data": "test data", "file": "test file", "url": null, "status": "Existing" }
    ]
  },
  {
    "productName": null,
    "productCategory": "hello category",
    "productImages": [
      { "content": "1", "id": null,"data": null, "file": null, "url": null, "status": "Existing"},
      { "content": "", "id": "234","data": "test data","file": "test file", "url": null, "status": "Existing" }
    ]
  },
  {
    "productName": "test product",
    "productCategory": "test category",
    "productImages": [ 
      { "content": "1", "id": "123", "data": "test data", "file": "test file", "url": null, "status": "Linked" },
      { "content": "2", "id": "234", "data": "test data", "file": "test file", "url": "test","status": "Linked" }
    ]
  },
  {
    "productName": "new product",
    "productCategory": "new category",
    "productImages": [
      { "content": "2","id": "32332", "data": "test data", "file": "test file", "url": "test", "status": "new" }
    ]
  }
]

const incomplete_items = [];
const new_data = []
for (const [index, item] of data.entries()) {
  if(item.productName && item.productCategory && item.productImages.every((image) => image.id && image.content && !['Existing', 'Linked'].includes(image.status))){
    new_data.push({
      productIndex: index,
      name: item.productName,
      category: item.productCategory,
      filteredImages: item.productImages.map((image) => ({ newcontent: image.content, id: image.id,  }))
    })
  } else {
    incomplete_items.push(index);
  }
}

if(incomplete_items.length) console.log(`Items ${incomplete_items} were missing required properties.`);
console.log(new_data);

一种直接的方法是基于两个 reducer 函数,每个函数都根据其实现目的正确命名,其中外部 reduce 任务迭代产品,内部任务迭代每个产品的相关图像。

因此,在每个 reduce 任务中都实现了预期结果项的过滤和 属性 映射。

const input = [{productName:null,productCategory:null,productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:null,productCategory:"hello category",productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:"test product",productCategory:"test category",productImages:[{content:"1",id:"123",data:"test data",file:"test file",url:null,status:"Linked"},{content:"2",id:"234",data:"test data",file:"test file",url:"test",status:"Linked"}]},{productName:"new product",productCategory:"new category",productImages:[{content:"2",id:"32332",data:"test data",file:"test file",url:"test",status:"new"}]}];

function collectNonMatchingImageItem(result, image) {
  const { id = null, content = null, status = null } = image;
  if (
    // loose truthyness comparison ... 1st stage filter (valid image) ...
    String(id ?? '').trim() &&
    String(content ?? '').trim() &&
    String(status ?? '').trim() &&

    // ... 2nd stage filter (non matching valid image item) ...
    !(/^linked|existing$/i).test(status)
  ) {
    // ... and mapping at once.
    result.push({
      id,
      newcontent: content,
    });
  }
  return result;
}
function collectValidProductWithNonMatchingImages(result, product, idx) {
  const {
    productName = null,
    productCategory = null,
    productImages = [],
  } = product;

  // loose truthyness comparison ... 1st stage filter (valid product) ...
  if (
    String(productName ?? '').trim() &&
    String(productCategory ?? '').trim()
  ) {
    const nonMatchingItems = productImages
      .reduce(collectNonMatchingImageItem, []);

    // 2nd stage filter (valid product with non matching valid images) ...
    if (nonMatchingItems.length >= 1) {

      // ... and mapping at once.
      result.push({
        productIndex: idx,
        name: productName,
        category: productCategory,
        filteredImages: nonMatchingItems,
      });
    }
  }
  return result;
}

const result = input
  .reduce(collectValidProductWithNonMatchingImages, []);

console.log({ result });
.as-console-wrapper { min-height: 100%!important; top: 0; }

编辑

"... How do you compare the old and new array. I wanted to output a warning if it isn't complete" – Joseph

如果涉及到新需求的集成,上述方法显示了它的灵活性...

const input = [{productName:null,productCategory:null,productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:null,productCategory:"hello category",productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:"test product",productCategory:"test category",productImages:[{content:"1",id:"123",data:"test data",file:"test file",url:null,status:"Linked"},{content:"2",id:"234",data:"test data",file:"test file",url:"test",status:"Linked"}]},{productName:"new product",productCategory:"new category",productImages:[{content:"2",id:"32332",data:"test data",file:"test file",url:"test",status:"new"}]}];

function collectNonMatchingImageItem(collector, image, idx) {
  const { id = null, content = null, status = null } = image;

  // loose truthyness comparison ... 1st stage filter (valid image) ...
  if (
    String(id ?? '').trim() &&
    String(content ?? '').trim() &&
    String(status ?? '').trim()
  ) {
    // ... 2nd stage filter (non matching valid image item) ...
    if (
      !(/^linked|existing$/i).test(status)
    ) {
      // ... and mapping at once.
      collector.result.push({
        id,
        newcontent: content,
      });
      collector.nonMatchingAt.push(idx);
    }
  } else {
    collector.invalidAt.push(idx);
  }
  return collector;
}
function collectValidProductWithNonMatchingImages(collector, product, idx) {
  const {
    productName = null,
    productCategory = null,
    productImages = [],
  } = product;

  // loose truthyness comparison ... 1st stage filter (valid product) ...
  if (
    String(productName ?? '').trim() &&
    String(productCategory ?? '').trim()
  ) {
    const {
      result: nonMatchingItems,
      nonMatchingAt,
      invalidAt,
    } = productImages.reduce(
      collectNonMatchingImageItem,
      { result: [], nonMatchingAt: [], invalidAt: [] },
    );
    // report related variables.
    let isPlural, itemPhrase, indexPhrase, imageReport;

    // 2nd stage filter (valid product with non matching valid images) ...
    if (nonMatchingItems.length >= 1) {

      // ... and mapping at once.
      collector.result.push({
        productIndex: idx,
        name: productName,
        category: productCategory,
        filteredImages: nonMatchingItems,
      });
      // create and collect report item.
      isPlural = (nonMatchingAt.length >= 2);

      itemPhrase = `item${ isPlural ? 's' : '' }`;
      indexPhrase = `${ isPlural ? 'indices' : 'index' }`;
      imageReport = `with non matching valid image ${ itemPhrase } at ${ indexPhrase } ${ nonMatchingAt }`;

      collector.report.push(
        `Valid product item at index ${ idx } ${ imageReport }.`
      );
    }
    if (invalidAt.length >= 1) {
      // create and collect report item.
      isPlural = (invalidAt.length >= 2);

      itemPhrase = `item${ isPlural ? 's' : '' }`;
      indexPhrase = `${ isPlural ? 'indices' : 'index' }`;
      imageReport = `with invalid image ${ itemPhrase } at ${ indexPhrase } ${ invalidAt }`;

      collector.report.push(
        `Valid product item at index ${ idx } ${ imageReport }.`
      );
    }
  } else {
    // create and collect report item.
    collector.report.push(
      `Invalid product item at index ${ idx }.`
    );
  }
  return collector;
}

const {
  result,
  report,
} = input.reduce(
  collectValidProductWithNonMatchingImages,
  { result: [], report: [] },
);
console.log({ result, report });
.as-console-wrapper { min-height: 100%!important; top: 0; }

您可以为此使用 reduce。

谢谢 Peter Seliger 指点我双倍减少。在可读性方面,你认为在外部减速器内部使用 2 reducers 而不是 filter().map() 更好吗?

const sampleData = [{productName:null,productCategory:null,productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:null,productCategory:"hello category",productImages:[{content:"1",id:null,data:null,file:null,url:null,status:"Existing"},{content:"",id:"234",data:"test data",file:"test file",url:null,status:"Existing"}]},{productName:"test product",productCategory:"test category",productImages:[{content:"1",id:"123",data:"test data",file:"test file",url:null,status:"Linked"},{content:"2",id:"234",data:"test data",file:"test file",url:"test",status:"Linked"}]},{productName:"new product",productCategory:"new category",productImages:[{content:"2",id:"32332",data:"test data",file:"test file",url:"test",status:"new"}]}];

const expectedData = sampleData.reduce((acc, val, i) => {

  // check if productName and productCategory are available
  if (val.productName && val.productCategory) {

    // get images that match the criteria and return object in desired format
    const filteredImages = val.productImages.reduce(
      (matchingImages, { content, id, status }) => {

        if (content && id && status !== "Existing" && status !== "Linked") {
          matchingImages.push({ newcontent: content, id });
        }
        return matchingImages;
      },
      []
    );

    // if images are available, all conditions are met
    if (filteredImages.length > 0) {
      acc.push({
        productIndex: i,
        name: val.productName,
        category: val.productCategory,
        filteredImages,
      });
    }
  }
  return acc;
}, []);

console.log(expectedData);
.as-console-wrapper { min-height: 100%!important; top: 0; }