Skip to content
Advertisement

How to filter array items, based on an item’s property key and value, with filter criteria that has to be obtained from yet another varying data pool?

I’m facing quite a challenging problem and I just want to find out if there is a better solution.

I have an object lets call it mainObject with a nested array data that looks like so:

{
  "arrayInObject": [
    {
      "date": "2021-08-27T09:44:31.059Z",
      "data": [
        {
          "ticketId": "abc",
          "type": "Food",
          "dateCreated": "2021-08-27T09:44:31.059Z"
        },
        {
          "ticketId": "efg",
          "type": "Drinks",
          "dateCreated": "2021-08-27T09:44:31.059Z"
        }
      ]
    }
  ]
}

and have an array that looks like

const arrayOne = [
 {
   label: "Cinema",
   type: 0,
   value: "aaaa-bbbb-cccc",
 },
 {
   id: "2",
   label: "Food",
   type: 1,
   value: "2",
 }, 
 {
   name: "1",
   label: "Drinks",
   type: 1,
   value: "1",
 }, 
];

I am pushing removing objects to and from arrayOne either with type 0 for cinema name, 1 for Snackbar categories from a different function, so arrayOne can contain one or multiple objects either type 0 or type 1

I am then filtering the array for all objects with the type 1 like so…

   const arrayOne = [
 {
   label: "Cinema",
   type: 0,
   value: "aaaa-bbbb-cccc",
 },
 {
   id: "2",
   label: "Food",
   type: 1,
   value: "2",
 }, 
 {
   name: "1",
   label: "Desserts",
   type: 1,
   value: "1",
 }, 
];

    const type = [1];
    const filteredArrayForKey = arrayOne.filter(item =>
      (type.indexOf(item.type) > -1)
    );
    const mapKeyToArray = filteredArrayForKey.map(item => 
    item.label);

Which returns a string array containing:

mapKeyToArray=["Food", "Desserts"]

Where I am getting confused is here: I want to filter through a 3rd object containing a nested Array and push those values to arrayOne to use as type: 2 to filter the array in mainObject with.

const ticketIds = {
   results: 2,
   ticketIds: [
      {
         ticketId: 'abc',
      },
      {
         ticketId: 'xyz',
      },
   ],
};
const mapKeyToArray = ["abc", "xyz"]

So the expected result would be the array in mainObject only returning objects that contain either type: 1 or type: 2 found in mapKeyToArray

const mapKeyToArray = ["Food", "Desserts", "abc", "xyz"]

{
  "arrayInObject": [
    {
      "date": "2021-08-27T09:44:31.059Z",
      "data": [
        {
          "ticketId": "tyu",
          "type": "Food",
          "dateCreated": "2021-08-27T09:44:31.059Z"
        },
        {
          "ticketId": "abc",
          "type": "Drinks",
          "dateCreated": "2021-08-27T09:44:31.059Z"
        }
      ]
    }
  ]
}

So very simply if the initial array in main object doesn’t contain any string values in the mapKeyToArray const, they are not returned in the rebuilt array. example:

const mapKeyToArray = ["abc", "Desserts"]

{
  "arrayInObject": [
    {
      "date": "2021-08-27T09:44:31.059Z",
      "data": [
        {
          "ticketId": "abc",
          "type": "Drinks",
          "dateCreated": "2021-08-27T09:44:31.059Z"
        }
      ]
    }
  ]
}

Advertisement

Answer

OP: “Let me sum it up. I am trying to only return the objects within the array in mainObject by either the label of any object in arrayOne containing the key type: 1 OR if the object in the array in mainObject contains a ticketId from the arrary in the ticketId object.”

This wish/requirement directly leads to a rule based boolean filter/validation approach which should be as generic to the operated data structure as possible. In this case the OP wants to achieve an entirely OR based filtering, which means some values each out of some specific property-keys.

For an example data item like …

{
  ticketId: "abc",
  type: "Food",
  dateCreated: "2021-08-27T09:44:31.059Z"
}

… and a list/array base rule set like …

[{
  key: 'type',
  values: [
    'Desserts',
  ]
}, {
  key: 'ticketId',
  values: [
    'abc',
  ]
}]

… one already could write a generic approach which does test any rule of a rule set against any provided data item …

const matchingRules = [{
  key: 'type',
  values: [
    'Desserts',
  ]
}, {
  key: 'ticketId',
  values: [
    'abc',
  ]
}];

let dataItem = {
  ticketId: "abc",
  type: "Food",
  dateCreated: "2021-08-27T09:44:31.059Z"
};
console.log(
  'does', dataItem, 'match some of', matchingRules, '?',
  matchingRules.some(rule =>
    rule.values.some(value =>
      dataItem[rule.key] === value
    )
  )
);

dataItem = {
  ticketId: "efg",
  type: "Drinks",
  dateCreated: "2021-08-27T09:44:31.059Z",
};
console.log(
  'does', dataItem, 'match some of', matchingRules, '?',
  matchingRules.some(rule =>
    rule.values.some(value =>
      dataItem[rule.key] === value
    )
  )
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Then one is just left with writing two functions, each creating a specific matching or filter rule from the OP’s provided data via reduce.

And in order to not always mutate the original data’s sub array, which one is going to operate on, one in addition needs to implement a functions which provides a deep/save enough copy of the original data structure.

function createTypeFilterRuleFromCategoryItems(list) {
  return list
    // create `type` based filter rule
    // upon a filter condition which
    // targets a category item's `type`.
    .reduce((rule, item) => {
      if (item.type === 1) {

        rule.values.push(item.label)
      }
      return rule;

    }, { key: 'type', values: [] });
}
const categoryList = [{
  label: "Cinema",
  type: 0,
  value: "aaaa-bbbb-cccc",
}, {
  id: "2",
  label: "Food",
  type: 1,
  value: "2",
}, {
  name: "1",
  label: "Drinks",
  type: 1,
  value: "1",
}];

console.log(
  'createTypeFilterRuleFromCategoryItems(categoryList) ...',
  createTypeFilterRuleFromCategoryItems(categoryList)
);


function createFilterRuleFromRandomBlobByKeys(blob, listKey, itemKey/*, targetKey*/) {
  return (blob?.[listKey] ?? [])
    // create filter rule based on an
    // additionally provided key set.
    .reduce((rule, item) => {
      if (item.hasOwnProperty(itemKey)) {

        rule.values.push(item[itemKey])
      }
      return rule;

    }, { key: itemKey/*targetKey*/, values: [] });
}
const randomBlob = {
  results: 2,
  ticketIds: [{
    ticketId: 'abc',
  }, {
    ticketId: 'xyz',
  }],
};

console.log(
  "createFilterRuleFromRandomBlobByKeys(randomBlob, 'ticketIds', 'ticketId') ...",
  createFilterRuleFromRandomBlobByKeys(randomBlob, 'ticketIds', 'ticketId')
);


function createDataCopy(blob) {
  const copy = { arrayInObject: [ { ...blob.arrayInObject[0] } ] };
  copy.arrayInObject[0].data =
    copy.arrayInObject[0].data.map(item => ({ ...item }));
  return copy;
}
const sampleData = {
  arrayInObject: [{
    date: "2021-08-27T09:44:31.059Z",
    data: [{
      ticketId: "abc",
      type: "Food",
      dateCreated: "2021-08-27T09:44:31.059Z",
    }, {
      ticketId: "efg",
      type: "Drinks",
      dateCreated: "2021-08-27T09:44:31.059Z",
    }],
  }],
};
const dataCopy = createDataCopy(sampleData);

// console.log({ dataCopy });


function matchItemByBoundFilterRules(item) {
  const ruleList = this;

  return ruleList.some(rule =>
    rule.values.some(value =>
      item[rule.key] === value
    )
  );
}

dataCopy.arrayInObject[0].data =
  dataCopy.arrayInObject[0].data.filter(matchItemByBoundFilterRules, [

    createTypeFilterRuleFromCategoryItems(categoryList),
    createFilterRuleFromRandomBlobByKeys(randomBlob, 'ticketIds', 'ticketId'),
  ]);
console.log('... after first filtering ...', { dataCopy });

dataCopy.arrayInObject[0].data =
  dataCopy.arrayInObject[0].data.filter(matchItemByBoundFilterRules, [{
    key: 'type',
    values: [
      'Desserts',
    ]
  }, {
    key: 'ticketId',
    values: [
      'abc',
    ]
  }]);
console.log('... after second filtering ...', { dataCopy });

console.log('... unmutaded original data ...', { sampleData });
.as-console-wrapper { min-height: 100%!important; top: 0; }

Edit

OP: “Nice approach. Yeah sure, please can you tell me the approaching you took to getting the matchingRules array from arrayOne and TicketIds?”

Actually the approaches are already provided/shown with the above example code.

Nevertheless both will be herby explained more in detail.

Regardless of what kind of source data structure one has to deal with, the target structure of a single rule always needs to be of this form …

{
  key: <propertyName:String>,
  values: [
    <arrayItem:any>[, <arrayItem:any>[, ...] ]
  ]
}

… which of cause means that the implementation of a function which creates such a rule is always determined by the data structure one has to derive such a rule from.

As for the first case with a data structure the OP refers to as arrayOne

[{
  label: "Cinema",
  type: 0,
  value: "aaaa-bbbb-cccc",
}, {
  id: "2",
  label: "Food",
  type: 1,
  value: "2",
}, {
  name: "1",
  label: "Drinks",
  type: 1,
  value: "1",
}];

… the OP 1stly wants to filter all items where each item’s type equals the number value 1. From each filtered item the OP then, in a 2nd step, wants to make an item’s label value part of the values array of a type specific match/filter rule. Thus a rule might look like this …

{
  "key": "type",
  "values": [
    "Food",
    "Drinks"
  ]
}

… and the rule creating function most probably will utilize Array.prototype.reduce and might be implemented as follows …

function createTypeFilterRuleFromCategoryItems(list) {
  return list
    // create `type` based filter rule
    // upon a filter condition which
    // targets a category item's `type`.
    .reduce((rule, item) => {
      if (item.type === 1) {

        rule.values.push(item.label)
      }
      return rule;

    }, { key: 'type', values: [] });
}
const categoryList = [{
  label: "Cinema",
  type: 0,
  value: "aaaa-bbbb-cccc",
}, {
  id: "2",
  label: "Food",
  type: 1,
  value: "2",
}, {
  name: "1",
  label: "Drinks",
  type: 1,
  value: "1",
}];

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

As for the 2nd case with a data structure the OP refers to as TicketIds but, from the question’s original and edited states, seems to be any generic blob of following structure …

const randomBlob = {
  results: 2,
  ticketIds: [{
    ticketId: 'abc',
  }, {
    ticketId: 'xyz',
  }],
};

… the OP wants to create a ticketId based match/filter rule like the following one …

{
  "key": "ticketId",
  "values": [
    "abc",
    "xyz"
  ]
}

Upon such a data structure, with maybe just varying property names, but a stable type base, a rule creating function can be implemented in a more generic way, based on e.g. reduce and Object.prototype.hasOwnProperty, like the following one …

function createFilterRuleFromRandomBlobByKeys(blob, listKey, itemKey/*, targetKey*/) {
  return (blob?.[listKey] ?? [])
    // create filter rule based on an
    // additionally provided key set.
    .reduce((rule, item) => {
      if (item.hasOwnProperty(itemKey)) {

        rule.values.push(item[itemKey])
      }
      return rule;

    }, { key: itemKey/*targetKey*/, values: [] });
}
const randomBlob = {
  results: 2,
  ticketIds: [{
    ticketId: 'abc',
  }, {
    ticketId: 'xyz',
  }],
};

console.log(
  "createFilterRuleFromRandomBlobByKeys(randomBlob, 'ticketIds', 'ticketId') ...",
  createFilterRuleFromRandomBlobByKeys(randomBlob, 'ticketIds', 'ticketId')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement