Brian M
Brian M

Reputation: 13

Filtering Levels of JSON in JavaScript recursively keeping children

I'm trying to filter a JSON in JavaScript on all levels. I want to keep the "name" of each of the entries at each level based on if it is present in an array. If it isn't present at a certain level, continue through the children.

Accepted names:

var accepted = ["top", "first", "three"]

Original JSON:

{
    "name": "top", 
    "children": [{
        "name": "first",
        "children": [{
            "name": "second",
            "children": [{ "name": "three" }]
        }, {
            "name": "second",
            "children": [{ "name": "three" }]
        }, {
            "name": "second",
            "children": [{ "name": "three" }]
        }]
    }, {
        "name": "first",
        "children": [{
            "name": "second",
            "children": [{ "name": "three" }]
        }, {
            "name": "second",
            "children": [{ "name": "three" }]
        }, {
            "name": "second",
            "children": [{ "name": "three" }]
        }]
    }]
}

New JSON:

{
    "name": "top",
    "children": [{
        "name": "first",
        "children": [
            { "name": "three" },
            { "name": "three" },
            { "name": "three" }
        ]
    }, {
        "name": "first",
        "children": [
            { "name": "three" },
            { "name": "three" },
            { "name": "three" }
        ]
    }]
}

What I've done:

var previous = ""
function loop(a) {
    if (previous.name == a.name) {
        previous = a
        previous['true_children'] = []
    }
    if (accepted.includes(a.name)) {
        previous['true_children'].push(a)
        if (a.children != null) {
            previous = a
            previous['true_children'] = []
        }
    }
    Array.isArray(a.children) && a.children.forEach(loop); 
}

My overall thinking for accomplishing this was to create a true_children property that could be iterated through the JSON rather than children. This new property could then only contain the filtered levels from the JSON.

However, my code seems to append the first once to true_children at the first level and again at the second level. Is there a fix for this or a better method to accomplishing this?

Upvotes: 0

Views: 350

Answers (3)

3limin4t0r
3limin4t0r

Reputation: 21130

Here is another recursive solution, that is hopefully not to hard to understand.

var accepted = ["top", "first", "three"],
    data = {"name": "top", "children": [{"name": "first", "children": [{"name": "second", "children": [{"name": "three"}]}, {"name": "second", "children": [{"name": "three"}]}, {"name": "second", "children": [{"name": "three"}]}]}, {"name": "first", "children": [{"name": "second", "children": [{"name": "three"}]}, {"name": "second", "children": [{"name": "three"}]}, {"name": "second", "children": [{"name": "three"}]}]}]};

function transform(accepted, data) { 
  const children = data.children || [],
        result = { name: data.name, children: [] },
        isAccepted = accepted.includes(data.name);

  children.forEach(child => {
    child = transform(accepted, child);
    const children = Array.isArray(child) ? child : [child];
    result.children.push(...children);
  });

  // Guard against data that isn't accepted.
  if (!isAccepted) return result.children;

  // Delete the children key if the array is empty.
  if (!children.length) delete result.children;

  return result;
}

console.log(transform(accepted, data));

Upvotes: 0

Slai
Slai

Reputation: 22876

For JSON string, the filtering can be done during parsing :

var accepted = ["top", "first", "three"], json = '{"name":"top","children":[{"name":"first","children":[{"name":"second","children":[{"name":"three"}]},{"name":"second","children":[{"name":"three"}]},{"name":"second","children":[{"name":"three"}]}]},{"name":"first","children":[{"name":"second","children":[{"name":"three"}]},{"name":"second","children":[{"name":"three"}]},{"name":"second","children":[{"name":"three"}]}]}]}'

var obj = JSON.parse(json, (k, v) => k == 'name' && !accepted.includes(v) ? void 0 :
                  v.children && v.children.length === 1 && !v.name ? v.children[0] : v)

console.log( obj )

Upvotes: 1

Guerric P
Guerric P

Reputation: 31815

I got your desired output with the following solution, hope that helps!

const accepted = [ "top", "first", "three"];

const data = { "name": "top", 
    "children": [
    {
    "name": "first",
    "children": [
        {
        "name": "second",
        "children":[
            {
            "name": "three"
            }
        ]
        },
        {
        "name": "second",
        "children":[
            {
            "name": "three"
            }
        ]
        },
        {
        "name": "second",
        "children":[
            {
            "name": "three"
            }
        ]
        }
    ]
    },
    {
    "name": "first",
    "children": [
        {
        "name": "second",
        "children":[
            {
            "name": "three"
            }
        ]
        },
        {
        "name": "second",
        "children":[
            {
            "name": "three"
            }
        ]
        },
        {
        "name": "second",
        "children":[
            {
            "name": "three"
            }
        ]
        }

    ]
    }
    ]
}

function buildOutput(input, accepted) {
  const filtered = Object.keys(input).reduce((accumulator, currentValue) => {
    if(currentValue === 'name' && accepted.find(x => input[currentValue] === x)) {
      accumulator[currentValue] = input[currentValue];
      if(input['children']){
        accumulator['children'] = input['children'].map(x => buildOutput(x, accepted)).filter(x => x);
        if(!accumulator['children'].length){
          accumulator['children'] = flattenChildren(input['children']).map(x => buildOutput(x, accepted)).filter(x => x);
        }
      }
    } 
    return accumulator;
  }, {});
  return Object.keys(filtered).length ? filtered : null;
}

function flattenChildren(children) {
  return children.reduce((accumulator, currentValue) => accumulator.concat(currentValue.children), []);
}

console.log(buildOutput(data, accepted));

Upvotes: 0

Related Questions