8

I have a tree object as below, I am trying to remove the items array property if it's empty. I am not sure on the best approach to do this?

I am thinking of looping through the key, check the property and then remove using delete myJSONObject[prop]... Any thoughts / ideas are welcome?

[{
    text: "TreeRoot",
    items: [{
        text: "Subgroup1",
        items: []
    }, {
        text: "Subgroup2",
        items: []
    }, {
        text: "Subgroup3",
        items: [],
        items: [{
            text: "subgroup5",
            items: [{
                text: "subgroup6",
                items: [{
                    text: "subgroup7",
                    items: [{
                        text: "subgroup8",
                        items: []
                    }]
                }]
            }]
        }]
    }]
}]
3
  • I would build a new object, rather than trying to modify this one. Commented Jan 15, 2013 at 18:39
  • Hey, you solved this yet? Commented Jul 8, 2015 at 9:02
  • stackoverflow.com/questions/9446426/… Commented Aug 29, 2015 at 10:42

5 Answers 5

5

This should do the job (ES5):

function removeEmpty(obj) {
  Object.keys(obj).forEach(function(key) {
    (key === 'items' && obj[key].length === 0) && delete obj[key] ||
    (obj[key] && typeof obj[key] === 'object') && removeEmpty(obj[key])
  });
  return obj;
};

JSBIN

Same in ES6:

const removeEmpty = (obj) => {
  Object.keys(obj).forEach(key =>
    (key === 'items' && obj[key].length === 0) && delete obj[key] ||
    (obj[key] && typeof obj[key] === 'object') && removeEmpty(obj[key])
  );
  return obj;
};

JSBIN

Sign up to request clarification or add additional context in comments.

Comments

1

You can have recursive algorithm that at each step either removes items array and returns, or recursively processes each individual object of the array.

I would also try to do this on the server-side. It will save a lot of complexity, memory, and processing time. There are usually ways of "excluding" empty arrays from the JSON encoded string.

Comments

0

Here is a solution using object-scan

// const objectScan = require('object-scan');

const data = [{ text: 'TreeRoot', items: [{ text: 'Subgroup1', items: [] }, { text: 'Subgroup2', items: [] }, { text: 'Subgroup3', items: [{ text: 'subgroup5', items: [{ text: 'subgroup6', items: [{ text: 'subgroup7', items: [{ text: 'subgroup8', items: [] }] }] }] }] }] }];

const modify = (obj) => objectScan(['**.items'], {
  rtn: 'count',
  filterFn: ({ value, parent, property }) => {
    if (Array.isArray(value) && value.length === 0) {
      delete parent[property];
      return true;
    }
    return false;
  }
})(obj);

console.log(modify(data)); // returns number of deletions
// => 3
console.log(data);
// => [ { text: 'TreeRoot', items: [ { text: 'Subgroup1' }, { text: 'Subgroup2' }, { text: 'Subgroup3', items: [ { text: 'subgroup5', items: [ { text: 'subgroup6', items: [ { text: 'subgroup7', items: [ { text: 'subgroup8' } ] } ] } ] } ] } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/[email protected]"></script>

Disclaimer: I'm the author of object-scan

Comments

0

This old question has been brought back up, and I think modern JS works well with recursion to yield a simple answer to do this in an immutable way:

const removeEmptyItems = (xs) =>
   xs .map (({items = [], ... rest}) => ({
    ... rest, 
    ... (items .length ? {items: removeEmptyItems(items)} : {})
  }))

const tree = [{text: "TreeRoot", items: [{text: "Subgroup1", items: []}, {text: "Subgroup2", items: []}, {text: "Subgroup3",items: [{text: "subgroup5", items: [{text: "subgroup6", items: [{text: "subgroup7", items: [{text: "subgroup8", items: []}]}]}]}]}]}]

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

This function simply maps over the array, keeping the rest of each object, and for its items property, skipping it when it's empty and recurring when it's not.

I've got to say, when the question was first asked, this answer would have looked quite foreign! Now it's just normal JS!

Comments

0

This is my (probably not perfect) TypeScript with Generics solution.

It tries to infer first level keyof - maybe some more advanced Guru may help :)

type MyObject = { [index: string]: unknown };

export function removeProperties<T extends MyObject, K extends keyof T>(obj: T, propsToRemove: K[]): Omit<T, K> {
  if (!obj) {
    return obj;
  }

  const newObj = { ...obj } as MyObject;
  Object.keys(newObj).forEach(key => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const value = obj[key] as any;

    if (propsToRemove.includes(key as K)) {
      delete newObj[key];
    } else if (typeof value === 'object' && value != null) {
      newObj[key] = removeProperties(value, propsToRemove);
    }
  });
  return newObj as Omit<T, K>;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.