Home > Enterprise >  How to filter and map nested array items while checking whether the to be filtered items meet certai
How to filter and map nested array items while checking whether the to be filtered items meet certai

Time:02-22

I need to filter and map both, array items and each item's child array items while checking whether the to be filtered items have certain properties which meet certain criteria.

What are good array method based approaches for filtering and mapping nested array items?

Required Properties

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

AND also if status inside productImages is not Linked or Existing

Sample 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"
      }
    ]
  }
]

Expected Output

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

Code

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

CodePudding user response:

You can do it like this:

  • map - to map the properties to new names and to return only the properties that are required
  • filter - to filter all items that have all the required properties, and save the indexes of the one that does not have all required properties
const incomplete_items = [];
const new_data = data
  .map((item, index) => ({productIndex:index, name: item.productName, category: item.productCategory, filteredImages: item.productImages.map((item) => ({ newcontent: item.content, id: item.id, status: item.status }))}))
  .filter((item, index) => {
    if(item.name && item.category && item.filteredImages.every((image) => image.id && image.newcontent && !['Existing', 'Linked'].includes(image.status))){
      return true;
    } else {
      incomplete_items.push(index);
      return false;
    }
  })
  .map(({filteredImages, ...item}) => ({...item, filteredImages: filteredImages.map((image)=> ({id: image.id, newcontent: image.newcontent})) }) )

Working example

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 = data
  .map((item, index) => ({productIndex:index, name: item.productName, category: item.productCategory, filteredImages: item.productImages.map((item) => ({ newcontent: item.content, id: item.id, status: item.status }))}))
  .filter((item, index) => {
    if(item.name && item.category && item.filteredImages.every((image) => image.id && image.newcontent && !['Existing', 'Linked'].includes(image.status))){
      return true;
    } else {
      incomplete_items.push(index);
      return false;
    }
  })
  .map(({filteredImages, ...item}) => ({...item, filteredImages: filteredImages.map((image)=> ({id: image.id, newcontent: image.newcontent})) }) )

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

CodePudding user response:

You're most of the way there, but you need a further check on the nested objects in the productImages array, here using every()

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

const complete = input.filter(o =>
  o.productName
  && o.productCategory
  && o.productImages.every(p => p.id && p.content));

console.log(complete);

To also rename properties you can apply the same filter logic as above, but rebuild the object with the new property names when it passes. Here using a for...of loop

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

const filtered = [];
let isAllComplete = true;
for (const o of input) {
  if (
    o.productName
    && o.productCategory
    && o.productImages.every(p => p.id && p.content)
  ) {
    filtered.push({
      name: o.productName,
      category: o.productCategory,
      filteredImages: o.productImages.map(({ content, id }) => ({ newcontent: content, id }))
    });
  } else {
    isAllComplete = false;
  }
}

console.log(isAllComplete
  ? 'All elements are complete'
  : 'Some elements were missing required properties');

console.log(filtered);

CodePudding user response:

A straightforward approach would be based on just two reducer functions, each being properly named according to its implemented purpose, where the outer reduce task iterates the products and the inner task iterates each product's related images.

Thus one achieves both within each reduce task, the filtering and the property mapping for the expected result items.

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 collectInvalidImageItem(result, { id, content, status }) {
  if (
    !(/^linked|existing$/i).test(status.trim())
  ) {
    // filtering and mapping at once.
    result.push({
      id,
      newcontent: content,
    });
  }
  return result;
}
function collectValidProductWithInvalidImageItems(result, product, idx) {
  const {
    productName = null,
    productCategory = null,
    productImages = []
  } = product;

  // loose truthyness comparison ... 1st stage filter.
  if (productName && productCategory) {

    const invalidItems = productImages
      .reduce(collectInvalidImageItem, []);

    if (invalidItems.length >= 1) {
      // 2nd stage filter and mapping at once.
      result.push({
        productIndex: idx,
        name: productName,
        category: productCategory,
        filteredImages: invalidItems,      
      })
    }
  }
  return result;
}
const result = input.reduce(collectValidProductWithInvalidImageItems, []);

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

CodePudding user response:

You can use reduce for this

const expectedData = sampleData.reduce((acc, val, i) => {
  if (val.productName && val.productCategory) {
    const filteredImages = val.productImages
      .filter(
        ({ content, id, status }) =>
          content && id && status !== "Existing" && status !== "Linked"
      )
      .map(({ content, id }) => {
        return { newcontent: content, id };
      });

    if (filteredImages.length > 0) {
      acc.push({
        productIndex: i,
        productName: val.productName,
        productCategory: val.productCategory,
        filteredImages,
      });
    }
  }
  return acc;
}, []);

console.log(expectedData);
  • Related