Prakash rathod
Prakash rathod

Reputation: 21

Filtering array of object using key in Javascript (node.js)

Please anyone can help, I want to filter data based on key in node.js, I've tried most of things, but didn't worked. Below example data which needs to be filtered. I could able to filter key:value pair, nested Json object but filtering Array of json object is not working. E.g. var data =

[{
    "createdBy":"Tom"
    "logistics" : 0,
    "paymentStatus" : "Paid",
    "orderAmount" : 5393.75,
    "details" : {
         "street": "S.S road",
         "postCOde": "440111",
    },
    "subOrders" : [
        {
        "name" : "sub Product1",
        "mrp" : 12,
        "details": "desk"
        },
        {
        "name" : "subProduct2",
        "mrp" : 89,
        "details": "chair"
        }
    ]
}]

result object should be filtered based on given permission array

var permissionArray = ['logistics','paymentStatus','details.street','subOrders.name','subOrders.details'];

filtered result should look like

{
   result = [{
        "logistics" : 0,
        "paymentStatus" : "Paid",
        "details" : {
             "street": "S.S road",
        },
        "subOrders" : [
            {
               "name" : "sub Product1",
               "details" : "desk"
            },
            {
               "name" : "sub Product1",
               "details" : "chair"
             }
         ]
}]
}

Upvotes: 1

Views: 838

Answers (5)

Nina Scholz
Nina Scholz

Reputation: 386868

You could take a two step approach where you group same keys in an array and store the nested properties as well to the grouped parents.

This is important if you have same parent keys, like

['subOrders.name', 'subOrders.mrp']

For example a grouped array looks like this:

[
    [
        "logistics"
    ],
    [
        "paymentStatus"
    ],
    [
        "details",
        ["street"]
    ],
    [
        "subOrders",
        ["name", "mrp"]
    ]
]

Then build new objects based on the key and nested keys.

function filter(keys) {

    var pathes = keys.reduce((r, path) => {
        var [key, ...rest] = path.split('.'),
            temp = r.find(([q]) => q === key);

        if (rest.length)
            if (temp) temp[1].push(rest.join('.'));
            else r.push([key, [rest.join('.')]]);
        else
            if (!temp) r.push([key]);

        return r;
    }, []);

    return function (object) {
        const keys = pathes.filter(([key]) => key in object);
        return Object.fromEntries(keys.map(([key, rest]) => [
            key,
            rest
                ? Array.isArray(object[key])
                    ? object[key].map(filter(rest))
                    : filter(rest)(object[key])
                : object[key]
        ]));
    };
}

var permissionArray = ['foo', 'logistics', 'paymentStatus', 'details.street', 'subOrders.name'],
    data = [{ createdBy: "Tom", logistics: 0, paymentStatus: "Paid", orderAmount: 5393.75, details: { street: "S.S road", postCOde: "440111", }, subOrders: [{ name: "sub Product1", mrp: 12 }, { name: "subProduct2", mrp: 89 }] }],
    result = data.map(filter(permissionArray));

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

Upvotes: 1

grodzi
grodzi

Reputation: 5703

Instead of building a filtered output, we may operate on original obj and remove its props.

The permissions represent a tree (or trie if node is a word (. delimited))

If we further assume that any specified path is accessible in data, then any leaf has for parent an array or object (and so forth).

We then build a tree, and for any node traverse data and remove the keys which are not part of our specification.

const data = {"createdBy":"Tom","logistics":0,"paymentStatus":"Paid","orderAmount":5393.75,"details":{"street":"S.S road","postCOde":"440111"},"subOrders":[{"name":"sub Product1","mrp":12},{"name":"subProduct2","mrp":89}],"a":{"b":[{"c":3,"d":"a"},{"c":4,"e":"f"}]}}
const d = JSON.parse(JSON.stringify(data)) // do not mutate original
const v = ['logistics','paymentStatus','details.street','subOrders.name','subOrders.details','a.b.c']
const tree = v.map(x => x.split('.')).reduce((o, p) => {
  p.reduce((c, x) => (c[x] = c[x] || {}), o)
  return o
},{})
function rec (keep, node) {
  if (Object.keys(keep).length === 0) return
  if (Array.isArray(node)) { return node.forEach(rec.bind(0, keep)) }
  for (const k in node) {
    !(k in keep) ? delete node[k] : rec(keep[k], node[k])
  }
}
rec(tree, d)
console.log(JSON.stringify(d,null,2))

Upvotes: 1

Alex L
Alex L

Reputation: 4241

Building on @radulle's answer to allow the 'details.name' dot notation desired:

(Also updated to now allow multiple subfields of one field - like 'subOrders.name' and 'subOrders.details')

var arr = 
[{
    "createdBy":"Tom",
    "logistics" : 0,
    "paymentStatus" : "Paid",
    "orderAmount" : 5393.75,
    "details" : {
         "street": "S.S road",
         "postCode": "440111"
    },
    "subOrders" : [
         {
        "name" : "sub Product1",
        "mrp" : 12,
        "details": "desk"
        },
        {
        "name" : "subProduct2",
        "mrp" : 89,
        "details": "chair"
        }
    ]
}]


var permissionArray = ['logistics','paymentStatus','details.street', 'details.postCode','subOrders.name', 'subOrders.details'];

var arrFiltered = arr.map(el => {
  let newEl = {}
  for (let elm of permissionArray){
    if (elm.includes('.')){
    
      //console.log(elm);
      const split_arr = elm.split('.')
      //console.log(el[split_arr[0]]);
      
      if (el[split_arr[0]] instanceof Array){
      
        if (!newEl.hasOwnProperty([split_arr[0]]) ){
          newEl[split_arr[0]] = el[split_arr[0]].map((child,index) => ({[split_arr[1]]: child[split_arr[1]]}) )
          
        } else {
          el[split_arr[0]].forEach((child,index) => {
            //console.log(child[ split_arr[1] ]);            
            newEl[split_arr[0]][index][split_arr[1]] = child[split_arr[1]];
          })
        }
        
      } else{

        if (!newEl.hasOwnProperty([split_arr[0]]) ){
          newEl[split_arr[0]] = {[split_arr[1]]: el[split_arr[0]][split_arr[1]]}
          
        } else {
          newEl[split_arr[0]][split_arr[1]] = el[split_arr[0]][split_arr[1]];
          
        }      
      }
                
    } else {
      newEl[elm] = el[elm];
    }    
  }
  return newEl;
  }
)

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

NOTE: you need to check first if a dot is included with elm.includes('.'), then you have to split it into its two parts with elm.split('.'), then finally you need to check if the value of the key is an array, like 'subOrders' or if it is just an object like 'details' and handle each case with either directly calling the child key, or mapping over the array and calling the child key.

With the permission array as:

var permissionArray = ['logistics','paymentStatus','details.street', 'details.postCode','subOrders.name', 'subOrders.details'];

The output is:

[
  {
    "logistics": 0,
    "paymentStatus": "Paid",
    "details": {
      "street": "S.S road",
      "postCode": "440111"
    },
    "subOrders": [
      {
        "name": "sub Product1",
        "details": "desk"
      },
      {
        "name": "subProduct2",
        "details": "chair"
      }
    ]
  }
]

NOTE: upon request of the OP, this now also allows you to have two subfields of one field like 'subOrders.name', 'subOrders.details'

For this !newEl.hasOwnProperty([split_arr[0]]) is used to check if the property/key doesn't already exist. If it does not, then create it, but if it does, then modify this existing property/key's value/item.

Upvotes: 2

Ben Aston
Ben Aston

Reputation: 55779

In the following solution, we divide the handling of objects and plain arrays.

We build-up a "qualified key" corresponding to the equivalent string in the permissions array, as we recursively descend into an object along its properties.

If we hit a primitive value, then we end the recursion for that property.

A new object sanitized is created for each level of the recursion to contain only the permitted properties.

const data = [{ "createdBy": "Tom", "logistics": 0, "paymentStatus": "Paid", "orderAmount": 5393.75, "details": { "street": "S.S road", "postCOde": "440111" }, "subOrders": [{ "name": "sub Product1", "mrp": 12 }, { "name": "subProduct2", "mrp": 89 }] }]

const isPrimitive = (x)=>x === null || typeof x !== "object"

function sanitize(o, permissions, prefix='') {
    if (isPrimitive(o)) return o
    const sanitized = {}
    for (const[k,v] of Object.entries(o)) {
        const qk = `${prefix}${k}`
        if (permissions.some((p)=>p.match(new RegExp(`^${qk}`)))) {
            sanitized[k] = Array.isArray(v) 
                ? sanitizeArray(v, permissions, `${qk}.`) 
                : sanitize(v, permissions, `${qk}.`)
        }
    }
    return sanitized
}

const sanitizeArray = (arr,permissions,prefix='')=>arr.map((el)=>sanitize(el, permissions, prefix))

const permissions = ['logistics', 'paymentStatus', 'details.street', 'subOrders.name']
const sanitized = sanitizeArray(data, permissions)
console.log(sanitized)

Upvotes: 0

radulle
radulle

Reputation: 1535

You can maybe do it like this:

var arr = 
[{
    "createdBy":"Tom",
    "logistics" : 0,
    "paymentStatus" : "Paid",
    "orderAmount" : 5393.75,
    "details" : {
         "street": "S.S road",
         "postCOde": "440111"
    },
    "subOrders" : [
        {
        "name" : "sub Product1",
        "mrp" : 12
        },
        {
        "name" : "subProduct2",
        "mrp" : 89
        }
    ]
}]


var permissionArray = ['logistics','paymentStatus','details']

var arrFiltered = arr.map(el => {
  let newEl = {}
  for (let elm of permissionArray) newEl[elm] = el[elm]
  return newEl
  }
)

console.log(arrFiltered)

Upvotes: 0

Related Questions