2

This question has been proposed many times in SO, but they all refer to an array of objects.

In my case, I would like to filter an object of objects.

Say I have this object:

"Users": {
  "w14FKo72BieZwbxwUouTpN7UQm02": {
    "name": "Naseebullah Ahmadi",
    "userType": "Patient",
    "writePermission": false
  },
  "SXMrXfBvexQUXfnVg5WWVwsKjpD2": {
    "name": "Levi Yeager",
    "userType": "Patient",
    "writePermission": false
  },
  "VoxHFgUEIwRFWg7JTKNXSSoFoMV2": {
    "name": "Ernest Kamavuako",
    "userType": "Doctor",
    "writePermission": true
  },
  "hFoWuyxv6Vbt8sEKA87T0720tXV2": {
    "name": "Karla Stanlee",
    "userType": "Doctor",
    "writePermission": true
  }
}

I would like to filter this so I can get the following:

"UsersCustom": {
  "w14FKo72BieZwbxwUouTpN7UQm02": {
    "name": "Naseebullah Ahmadi",
    "userType": "Patient",
    "writePermission": false
  },
  "SXMrXfBvexQUXfnVg5WWVwsKjpD2": {
    "name": "Levi Yeager",
    "userType": "Patient",
    "writePermission": false
  }
}

What's the point of doing this?

Note that this object "User" is huge in reality (over 1000 entries) and each user has more attributes than mere "name", "userType", and "writePermission".

The reason why I need to filter the user's object is so that I can get users who are only (Patient) and get the id of that Patient to lookup in another object and finally merge them all in one object.

What I have so far

// user is the object as seen above
let Patients = users ? (
  // I loop through them
  Object.keys(users).map((uid, i) => {
    // get individual patient
    const patient = users[uid];
    // check their userType
    if (patient.userType === "Patient") {
      let customPatient = {
        uid: uid,
        name: patient.name,
        profession: patient.profession,
        history: null,
        ecg: null,
        heartSound: null
      };

      this._healthRef(uid).then(health => {
        customPatient.history = health;
        return this._heartSoundRef(uid).then(HS => HS);
      }).then(HS => {
        customPatient.heartSound = HS;
        return this._ecgRef(uid).then(a => a);
      }).then(ecg => {
        customPatient.ecg = ecg;
      }).then(() => {
        cusomPatients.push(customPatient);
      })
    }
  })
)

My solution above is, although partially complete, is still not efficient and wrong. Because I need the Id of each patient for other lookup

Update

Currently, most solution provides looping through the entries, which means that it will run O(n) in worst case. Is there any possibility that it can be solved quicker than O(n)?

Doe
  • 193
  • 4
  • 14

5 Answers5

5

Using reduce method you can get your object

var data = {
  "w14FKo72BieZwbxwUouTpN7UQm02": {
    "name": "Naseebullah Ahmadi",
    "userType": "Patient",
    "writePermission": false
  },
  "SXMrXfBvexQUXfnVg5WWVwsKjpD2": {
    "name": "Levi Yeager",
    "userType": "Patient",
    "writePermission": false
  },
  "VoxHFgUEIwRFWg7JTKNXSSoFoMV2": {
    "name": "Ernest Kamavuako",
    "userType": "Doctor",
    "writePermission": true
 },
 "hFoWuyxv6Vbt8sEKA87T0720tXV2": {
   "name": "Karla Stanlee",
   "userType": "Doctor",
   "writePermission": true
 }

};

var results = Object.keys(data).reduce(function(acc, val) {
    if(data[val].userType === 'Patient')  acc[val] = data[val];
  return acc;
}, {});
console.log(results);
fyasir
  • 2,924
  • 2
  • 23
  • 36
1

My guess is that filtering during parsing might be most efficient (haven't measured/compaed it):

j = '{"w14FKo72BieZwbxwUouTpN7UQm02":{"name":"Naseebullah Ahmadi","userType":"Patient","writePermission":false},"SXMrXfBvexQUXfnVg5WWVwsKjpD2":{"name":"Levi Yeager","userType":"Patient","writePermission":false},"VoxHFgUEIwRFWg7JTKNXSSoFoMV2":{"name":"Ernest Kamavuako","userType":"Doctor","writePermission":true},"hFoWuyxv6Vbt8sEKA87T0720tXV2":{"name":"Karla Stanlee","userType":"Doctor","writePermission":true}}'

o = JSON.parse(j, (k, v) => !v.userType || v.userType === 'Patient' ? v : void 0)

console.log(o)

Caching the filtered object would be even more efficient to reduce network traffic.

Slai
  • 22,144
  • 5
  • 45
  • 53
  • @Doe: with all due respect, how does this answer have anything to do with the question you asked? – georg Feb 18 '18 at 12:34
  • @georg it's very likely that the object comes as a JSON string and has to be parsed, so filtering during the parsing avoids some of the extra memory (de)allocations and enumerating the object keys. My estimate is that should be more than 2 times faster on average (compared to parsing all and filtering after), but I am still curious to see any time comparison results. – Slai Feb 18 '18 at 13:08
  • @georg I'll update my question to explain the reason why I chose this as the accepted answer. But bear in mind, this answer was only suitable in my instance. Whereas in another case it might not. I'll get back to you on this – Doe Feb 18 '18 at 13:44
  • @Slai Indeed the object does come in JSON format. (Its fetched from firebase database) and I believe this was elegant solution for that. :) – Doe Feb 18 '18 at 13:46
  • @Slai I'm indeed more curious as to what you meant by caching the JSON. I understand the concept of caching in the realm of react native. – Doe Feb 18 '18 at 13:47
  • @Doe I am not familiar with firebase, but I feel like there should be a better way to filter on the server side and get just the filtered result, instead of receiving all and filtering the result. I am guessing you might have gotten a better answer if you included the firebase detail and tag in the question. By caching I mean storing the filtered result in local storage or another place to request the result from, but that might not be as applicable in your case if the original data is updated more often than it is requested. – Slai Feb 18 '18 at 14:15
0

You can solve this with for-in or Object.keys or (on modern JavaScript engines) Object.entries.

for-in provides a means of iterating through the object's enumerable properties; you can then use those property names to look up the value:

var result = {};
for (var key in users) {
    var entry = users[key];
    if (entry.userType === "Patient") {
        result[key] = entry;
    }
}

Live Example:

var users = {
  "w14FKo72BieZwbxwUouTpN7UQm02": {
    "name": "Naseebullah Ahmadi",
    "userType": "Patient",
    "writePermission": false
  },
  "SXMrXfBvexQUXfnVg5WWVwsKjpD2": {
    "name": "Levi Yeager",
    "userType": "Patient",
    "writePermission": false
  },
  "VoxHFgUEIwRFWg7JTKNXSSoFoMV2": {
    "name": "Ernest Kamavuako",
    "userType": "Doctor",
    "writePermission": true
  },
  "hFoWuyxv6Vbt8sEKA87T0720tXV2": {
    "name": "Karla Stanlee",
    "userType": "Doctor",
    "writePermission": true
  }
};

var result = {};
for (var key in users) {
    var entry = users[key];
    if (entry.userType === "Patient") {
        result[key] = entry;
    }
}

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

Object.keys gives you an array of the object's own enumerable properties, so:

var result = {};
Object.keys(users).forEach(function(key) {
    var entry = users[key];
    if (entry.userType === "Patient") {
        result[key] = entry;
    }
});

Live Example:

var users = {
  "w14FKo72BieZwbxwUouTpN7UQm02": {
    "name": "Naseebullah Ahmadi",
    "userType": "Patient",
    "writePermission": false
  },
  "SXMrXfBvexQUXfnVg5WWVwsKjpD2": {
    "name": "Levi Yeager",
    "userType": "Patient",
    "writePermission": false
  },
  "VoxHFgUEIwRFWg7JTKNXSSoFoMV2": {
    "name": "Ernest Kamavuako",
    "userType": "Doctor",
    "writePermission": true
  },
  "hFoWuyxv6Vbt8sEKA87T0720tXV2": {
    "name": "Karla Stanlee",
    "userType": "Doctor",
    "writePermission": true
  }
};

var result = {};
Object.keys(users).forEach(function(key) {
    var entry = users[key];
    if (entry.userType === "Patient") {
        result[key] = entry;
    }
});

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

Object.entries provides an iterable which consists of the name and value of each own, enumerable property. It's quite new. So for instance (in ES2015+ syntax where Object.entries was added in ES2017 but can be polyfilled):

const result = {};
for (const [key, value] of Object.entries(users)) {
    if (value.userType === "Patient") {
        result[key] = value;
    }
}

Live Example:

const users = {
  "w14FKo72BieZwbxwUouTpN7UQm02": {
    "name": "Naseebullah Ahmadi",
    "userType": "Patient",
    "writePermission": false
  },
  "SXMrXfBvexQUXfnVg5WWVwsKjpD2": {
    "name": "Levi Yeager",
    "userType": "Patient",
    "writePermission": false
  },
  "VoxHFgUEIwRFWg7JTKNXSSoFoMV2": {
    "name": "Ernest Kamavuako",
    "userType": "Doctor",
    "writePermission": true
  },
  "hFoWuyxv6Vbt8sEKA87T0720tXV2": {
    "name": "Karla Stanlee",
    "userType": "Doctor",
    "writePermission": true
  }
};

const result = {};
for (const [key, value] of Object.entries(users)) {
    if (value.userType === "Patient") {
        result[key] = value;
    }
}

console.log(result);
.as-console-wrapper {
  max-height: 100% !important;
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

You can use reduce() method on your object keys and build new object from that.

const obj = {"Users":{"w14FKo72BieZwbxwUouTpN7UQm02":{"name":"Naseebullah Ahmadi","userType":"Patient","writePermission":false},"SXMrXfBvexQUXfnVg5WWVwsKjpD2":{"name":"Levi Yeager","userType":"Patient","writePermission":false},"VoxHFgUEIwRFWg7JTKNXSSoFoMV2":{"name":"Ernest Kamavuako","userType":"Doctor","writePermission":true},"hFoWuyxv6Vbt8sEKA87T0720tXV2":{"name":"Karla Stanlee","userType":"Doctor","writePermission":true}}}

const newObj = {
  'UserCustom': Object.keys(obj.Users).reduce((r, k) => {
    if(obj.Users[k].userType == 'Patient') r[k] = Object.assign({}, obj.Users[k])
    return r;
  }, {})
}
  
console.log(newObj)
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
  • Is this efficient compared to object.entries of for in? – Doe Feb 17 '18 at 14:06
  • With entries you also get array and loop it, and with for in you loop object so first one should be similar and i am not sure for second one, better test it. – Nenad Vracar Feb 17 '18 at 14:19
0

Use for...in to iterate through object and check if ther usertype is patient or not.

var users = {
  "Users": {
    "w14FKo72BieZwbxwUouTpN7UQm02": {
      "name": "Naseebullah Ahmadi",
      "userType": "Patient",
      "writePermission": false
    },
    "SXMrXfBvexQUXfnVg5WWVwsKjpD2": {
      "name": "Levi Yeager",
      "userType": "Patient",
      "writePermission": false
    },
    "VoxHFgUEIwRFWg7JTKNXSSoFoMV2": {
      "name": "Ernest Kamavuako",
      "userType": "Doctor",
      "writePermission": true
    },
    "hFoWuyxv6Vbt8sEKA87T0720tXV2": {
      "name": "Karla Stanlee",
      "userType": "Doctor",
      "writePermission": true
    }
  }
};

for(let user in users.Users){
  if(users.Users.hasOwnProperty(user) && users.Users[user].userType!=="Patient")
    delete users.Users[user];
}

console.log(users);

This will modify the object, if you want to preserve it then just do a deep copy of the original object.

void
  • 36,090
  • 8
  • 62
  • 107
  • I admire your solution, but quick question, the solution provides O(n) in worst case, and loops through the whole object. Can my problem be solved any faster than O(n)? – Doe Feb 17 '18 at 14:07
  • I dont think so, it will atleast need to see all the object to check if it is patient or not, so complexity less than O(n) will not be possible... – void Feb 17 '18 at 14:08
  • As I suspected. I agree! – Doe Feb 17 '18 at 14:09