1
\$\begingroup\$

I have some code that takes some JSON, parses it, and given an optional filter, will attempt to find any key (or nested key) that includes the filter (partial matching - it does not match values). The difficult part was handling deeply nested objects and arrays.

For example, given the following JSON:

{
  "a": {
    "b": 2,
    "c": 3,
    "d": [{ "b": 4, "c": 4 }, { "e": 5 }]
  }
}

If I filter with the key 'b' the output should retain the object in 'a' and the first object in 'd':

{
  a: {
    b: 2,
    d: [{ b: 4 }]
  }
}

The same goes with arrays - I must search down into an array of objects and retain the array if a nested object has a key that includes the filter.

The following is an example, filtering out user data by 'id':

const json = '[{"_id":"5bbce9314d255887d3583cb1","index":0,"guid":"05b45bf7-dc1e-4db0-a378-f29c7967c36e","isActive":false,"balance":"$1,099.18","picture":"http://placehold.it/32x32","age":23,"eyeColor":"brown","name":"Polly Hammond","gender":"female","company":"NETILITY","email":"pollyhammond@netility.com","phone":"+1 (953) 480-3232","address":"438 Jardine Place, Brandywine, Minnesota, 238","about":"Fugiat mollit reprehenderit adipisicing minim irure amet dolor excepteur veniam laborum. Velit velit ullamco nostrud laboris enim ex reprehenderit magna esse voluptate et et velit elit. Lorem adipisicing do elit enim esse ad. Esse ea ex ex labore aute aliquip duis elit voluptate proident cillum. Esse qui duis reprehenderit cillum ad proident proident. Incididunt incididunt culpa labore laborum proident labore adipisicing nisi dolor. Dolore cillum do officia cupidatat sit enim deserunt incididunt dolore reprehenderit ullamco adipisicing est.","registered":"2017-04-10T09:25:00 +04:00","latitude":-65.127935,"longitude":-124.785612,"tags":["cupidatat","tempor","duis","magna","culpa","magna","duis"],"friends":[{"id":0,"name":"Dawson Rollins"},{"id":1,"name":"Roberts Carroll"},{"id":2,"name":"Bowen Farley"}],"greeting":"Hello, Polly Hammond! You have 1 unread messages.","favoriteFruit":"strawberry"},{"_id":"5bbce93106cdc523057314ef","index":1,"guid":"85fa0c63-d97c-4592-9b87-85953ad4172b","isActive":false,"balance":"$1,401.63","picture":"http://placehold.it/32x32","age":37,"eyeColor":"green","name":"Shelley Carrillo","gender":"female","company":"SONIQUE","email":"shelleycarrillo@sonique.com","phone":"+1 (880) 428-3366","address":"679 Fountain Avenue, Enoree, Puerto Rico, 1210","about":"Culpa labore sit velit Lorem officia proident aute duis ullamco non est. Lorem veniam voluptate non ullamco cillum nisi id sint esse. Id labore sunt incididunt laboris eu nulla esse do proident commodo est commodo nisi nulla. Sunt sit aute eu pariatur irure laboris minim sint consectetur magna dolore occaecat deserunt. In aute proident ex culpa ea adipisicing ea laborum irure.","registered":"2016-08-05T01:07:31 +04:00","latitude":-34.270418,"longitude":-146.16793,"tags":["adipisicing","reprehenderit","ipsum","quis","do","excepteur","aliquip"],"friends":[{"id":0,"name":"Gross Murray"},{"id":1,"name":"Norman Hubbard"},{"id":2,"name":"Winnie Reed"}],"greeting":"Hello, Shelley Carrillo! You have 5 unread messages.","favoriteFruit":"apple"}]'

const isObject = o => typeof o === 'object' && o !== null;
const isArray = Array.isArray;

const parseJson = (json, filter) => {
  const pJson = JSON.parse(json);

  const parseArray = array => {
    // remove non-objects from the array
    const arrayObjects = array.filter(isObject);

    return arrayObjects.map(o => {
      if (isArray(o)) {
        return parseArray(o);
      } else if (isObject(o)) {
        return parseObject(o);
      }
    }).filter(o => isArray(o) ? o.length : Object.keys(o).length);
  };

  const parseObject = object => {
    if (isArray(object)) {
      return parseArray(object);
    }

    let output = {};
    const keys = Object.keys(object);

    for (const key of keys) {
      if (isArray(object[key])) {
        const array = parseArray(object[key]);
        
        if (array && array.length) {
          output[key] = array;
        } else if (key.includes(filter)) {
          output[key] = [];
        }
      } else if (isObject(object[key])) {
        const nestedObject = parseObject(object[key]);
        
        if (Object.keys(nestedObject).length) {
          output[key] = {...nestedObject};
        } else if (key.includes(filter)) {
          output[key] = {};
        }
      } else if (key.includes(filter)) {
        // store the value on output if the property name has a match
        output[key] = object[key];
      }
    }

    return output;
  };

  return filter
    ? parseObject(pJson)
    : pJson;
};

console.log(parseJson(json, 'id'));

The code works, and seems to perform decently even with large sets of data, but it feels a bit messy due to having to check multiple times whether I'm dealing with an array or an object. Since it's a personal project I'm not opposed to shortcuts/operators/etc. that would normally be frowned upon in a shared project.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.