2

I have a nested JS object that looks like this:

data = {
    "name": "root",
    "children": [{
            "name": "bob",
            "children": []
        },
        {
            "name": "susan",
            "children": []
        },
        {
            "name": "chris",
            "children": []
        },
        {
            "name": "bobo",
            "children": []
        },
        {
            "name": "heather",
            "children": []
        }
    ]
}

I can delete any property, regardless of depth, by using the following recursive function:

function search_and_delete(obj, search_term) {
    for (var k in obj) {
        if (typeof obj[k] == "object" && obj[k] !== null) {
            search_and_delete(obj[k], search_term)
        } else {
            if (typeof(obj.name) !== 'undefined') {
                if (obj.name === search_term) {
                    delete obj.name
                    delete obj.children
                }
            }
        }
    }
}

For example:

search_and_delete(data, 'bob')

...produces the following result:

{
    "name": "root",
    "children": [
       {},   // <------ how do I remove this
       {
            "name": "susan",
            "children": []
       },
       {
            "name": "chris",
            "children": []
       },
       {
            "name": "bobo",
            "children": []
       },
       {
            "name": "heather",
            "children": []
       }
    ]
}

But this leaves an empty object where bob used to be. How can I remove this object as well and reindex the JSON structure as if it were never there?

UPDATE

@CertainPerformance answer is the closest but produces the following error:

If you use this flare data:

and run the following:

search_and_delete(data, "Interpolator", data)

It does this:

enter image description here

7
  • @Tibrogargan no I tried this, that doesn't work. You cannot delete entire objects with Javascript's delete, only the object's properties. Commented Aug 17, 2019 at 21:08
  • @Cybernetic use filter instead. You're doing a depth first search which is going to search all the children of a node even if the node should be deleted. And it's still not a JSON structure, it's a a JavaScript object Commented Aug 17, 2019 at 21:11
  • @CertainPerformance JavaScript arrays are sparse by default. Commented Aug 17, 2019 at 21:14
  • @Tibrogargan Not at all. Sparse arrays are rare, have to be made deliberately (like with delete, or by assigning to an array's index when previous indicies don't exist yet), and almost always a sign of bad code Commented Aug 17, 2019 at 21:15
  • @CertainPerformance they are still sparse arrays, even if they have no missing elements. Commented Aug 17, 2019 at 21:16

2 Answers 2

4

One option is to pass down the parent array to the recursive call so that you can splice the found item out of it.

Because mutating an array will change what each property points to, that will make it difficult to use for..in - use Object.values to iterate over the object (or array) instead:

const data = {
    "name": "root",
    "children": [{
            "name": "bob",
            "children": []
        },
        {
            "name": "susan",
            "children": []
        },
        {
            "name": "chris",
            "children": []
        },
        {
            "name": "bobo",
            "children": []
        },
        {
            "name": "heather",
            "children": []
        }
    ]
}

function search_and_delete(obj, search_term, parent) {
  if (obj.name === search_term) {
    parent.splice(parent.indexOf(obj), 1);
  } else {
    Object.values(obj).forEach((val) => {
      if (val && typeof val === "object") {
          search_and_delete(val, search_term, obj)
      }
    });
  }
}

search_and_delete(data, 'bob');
console.log(data);

Example of using this with a deeper structure:

const data = {
 "name": "flare",
 "children": [
  {
   "name": "analytics",
   "children": [
    {
     "name": "cluster",
     "children": [
      {"name": "AgglomerativeCluster", "value": 3938},
      {"name": "CommunityStructure", "value": 3812},
      {"name": "HierarchicalCluster", "value": 6714},
      {"name": "MergeEdge", "value": 743}
     ]
    },
    {
     "name": "graph",
     "children": [
      {"name": "BetweennessCentrality", "value": 3534},
      {"name": "LinkDistance", "value": 5731},
      {"name": "MaxFlowMinCut", "value": 7840},
      {"name": "ShortestPaths", "value": 5914},
      {"name": "SpanningTree", "value": 3416}
     ]
    },
    {
     "name": "optimization",
     "children": [
      {"name": "AspectRatioBanker", "value": 7074}
     ]
    }
   ]
  },
  {
   "name": "animate",
   "children": [
    {"name": "Easing", "value": 17010},
    {"name": "FunctionSequence", "value": 5842},
    {
     "name": "interpolate",
     "children": [
      {"name": "ArrayInterpolator", "value": 1983},
      {"name": "ColorInterpolator", "value": 2047},
      {"name": "DateInterpolator", "value": 1375},
      {"name": "Interpolator", "value": 8746},
      {"name": "MatrixInterpolator", "value": 2202},
      {"name": "NumberInterpolator", "value": 1382},
      {"name": "ObjectInterpolator", "value": 1629},
      {"name": "PointInterpolator", "value": 1675},
      {"name": "RectangleInterpolator", "value": 2042}
     ]
    },
    {"name": "ISchedulable", "value": 1041},
    {"name": "Parallel", "value": 5176},
    {"name": "Pause", "value": 449},
    {"name": "Scheduler", "value": 5593},
    {"name": "Sequence", "value": 5534},
    {"name": "Transition", "value": 9201},
    {"name": "Transitioner", "value": 19975},
    {"name": "TransitionEvent", "value": 1116},
    {"name": "Tween", "value": 6006}
   ]
  },
  {
   "name": "data",
   "children": [
    {
     "name": "converters",
     "children": [
      {"name": "Converters", "value": 721},
      {"name": "DelimitedTextConverter", "value": 4294},
      {"name": "GraphMLConverter", "value": 9800},
      {"name": "IDataConverter", "value": 1314},
      {"name": "JSONConverter", "value": 2220}
     ]
    },
    {"name": "DataField", "value": 1759},
    {"name": "DataSchema", "value": 2165},
    {"name": "DataSet", "value": 586},
    {"name": "DataSource", "value": 3331},
    {"name": "DataTable", "value": 772},
    {"name": "DataUtil", "value": 3322}
   ]
  },
  {
   "name": "display",
   "children": [
    {"name": "DirtySprite", "value": 8833},
    {"name": "LineSprite", "value": 1732},
    {"name": "RectSprite", "value": 3623},
    {"name": "TextSprite", "value": 10066}
   ]
  },
  {
   "name": "flex",
   "children": [
    {"name": "FlareVis", "value": 4116}
   ]
  },
  {
   "name": "physics",
   "children": [
    {"name": "DragForce", "value": 1082},
    {"name": "GravityForce", "value": 1336},
    {"name": "IForce", "value": 319},
    {"name": "NBodyForce", "value": 10498},
    {"name": "Particle", "value": 2822},
    {"name": "Simulation", "value": 9983},
    {"name": "Spring", "value": 2213},
    {"name": "SpringForce", "value": 1681}
   ]
  },
  {
   "name": "query",
   "children": [
    {"name": "AggregateExpression", "value": 1616},
    {"name": "And", "value": 1027},
    {"name": "Arithmetic", "value": 3891},
    {"name": "Average", "value": 891},
    {"name": "BinaryExpression", "value": 2893},
    {"name": "Comparison", "value": 5103},
    {"name": "CompositeExpression", "value": 3677},
    {"name": "Count", "value": 781},
    {"name": "DateUtil", "value": 4141},
    {"name": "Distinct", "value": 933},
    {"name": "Expression", "value": 5130},
    {"name": "ExpressionIterator", "value": 3617},
    {"name": "Fn", "value": 3240},
    {"name": "If", "value": 2732},
    {"name": "IsA", "value": 2039},
    {"name": "Literal", "value": 1214},
    {"name": "Match", "value": 3748},
    {"name": "Maximum", "value": 843},
    {
     "name": "methods",
     "children": [
      {"name": "add", "value": 593},
      {"name": "and", "value": 330},
      {"name": "average", "value": 287},
      {"name": "count", "value": 277},
      {"name": "distinct", "value": 292},
      {"name": "div", "value": 595},
      {"name": "eq", "value": 594},
      {"name": "fn", "value": 460},
      {"name": "gt", "value": 603},
      {"name": "gte", "value": 625},
      {"name": "iff", "value": 748},
      {"name": "isa", "value": 461},
      {"name": "lt", "value": 597},
      {"name": "lte", "value": 619},
      {"name": "max", "value": 283},
      {"name": "min", "value": 283},
      {"name": "mod", "value": 591},
      {"name": "mul", "value": 603},
      {"name": "neq", "value": 599},
      {"name": "not", "value": 386},
      {"name": "or", "value": 323},
      {"name": "orderby", "value": 307},
      {"name": "range", "value": 772},
      {"name": "select", "value": 296},
      {"name": "stddev", "value": 363},
      {"name": "sub", "value": 600},
      {"name": "sum", "value": 280},
      {"name": "update", "value": 307},
      {"name": "variance", "value": 335},
      {"name": "where", "value": 299},
      {"name": "xor", "value": 354},
      {"name": "_", "value": 264}
     ]
    },
    {"name": "Minimum", "value": 843},
    {"name": "Not", "value": 1554},
    {"name": "Or", "value": 970},
    {"name": "Query", "value": 13896},
    {"name": "Range", "value": 1594},
    {"name": "StringUtil", "value": 4130},
    {"name": "Sum", "value": 791},
    {"name": "Variable", "value": 1124},
    {"name": "Variance", "value": 1876},
    {"name": "Xor", "value": 1101}
   ]
  },
  {
   "name": "scale",
   "children": [
    {"name": "IScaleMap", "value": 2105},
    {"name": "LinearScale", "value": 1316},
    {"name": "LogScale", "value": 3151},
    {"name": "OrdinalScale", "value": 3770},
    {"name": "QuantileScale", "value": 2435},
    {"name": "QuantitativeScale", "value": 4839},
    {"name": "RootScale", "value": 1756},
    {"name": "Scale", "value": 4268},
    {"name": "ScaleType", "value": 1821},
    {"name": "TimeScale", "value": 5833}
   ]
  },
  {
   "name": "util",
   "children": [
    {"name": "Arrays", "value": 8258},
    {"name": "Colors", "value": 10001},
    {"name": "Dates", "value": 8217},
    {"name": "Displays", "value": 12555},
    {"name": "Filter", "value": 2324},
    {"name": "Geometry", "value": 10993},
    {
     "name": "heap",
     "children": [
      {"name": "FibonacciHeap", "value": 9354},
      {"name": "HeapNode", "value": 1233}
     ]
    },
    {"name": "IEvaluable", "value": 335},
    {"name": "IPredicate", "value": 383},
    {"name": "IValueProxy", "value": 874},
    {
     "name": "math",
     "children": [
      {"name": "DenseMatrix", "value": 3165},
      {"name": "IMatrix", "value": 2815},
      {"name": "SparseMatrix", "value": 3366}
     ]
    },
    {"name": "Maths", "value": 17705},
    {"name": "Orientation", "value": 1486},
    {
     "name": "palette",
     "children": [
      {"name": "ColorPalette", "value": 6367},
      {"name": "Palette", "value": 1229},
      {"name": "ShapePalette", "value": 2059},
      {"name": "SizePalette", "value": 2291}
     ]
    },
    {"name": "Property", "value": 5559},
    {"name": "Shapes", "value": 19118},
    {"name": "Sort", "value": 6887},
    {"name": "Stats", "value": 6557},
    {"name": "Strings", "value": 22026}
   ]
  },
  {
   "name": "vis",
   "children": [
    {
     "name": "axis",
     "children": [
      {"name": "Axes", "value": 1302},
      {"name": "Axis", "value": 24593},
      {"name": "AxisGridLine", "value": 652},
      {"name": "AxisLabel", "value": 636},
      {"name": "CartesianAxes", "value": 6703}
     ]
    },
    {
     "name": "controls",
     "children": [
      {"name": "AnchorControl", "value": 2138},
      {"name": "ClickControl", "value": 3824},
      {"name": "Control", "value": 1353},
      {"name": "ControlList", "value": 4665},
      {"name": "DragControl", "value": 2649},
      {"name": "ExpandControl", "value": 2832},
      {"name": "HoverControl", "value": 4896},
      {"name": "IControl", "value": 763},
      {"name": "PanZoomControl", "value": 5222},
      {"name": "SelectionControl", "value": 7862},
      {"name": "TooltipControl", "value": 8435}
     ]
    },
    {
     "name": "data",
     "children": [
      {"name": "Data", "value": 20544},
      {"name": "DataList", "value": 19788},
      {"name": "DataSprite", "value": 10349},
      {"name": "EdgeSprite", "value": 3301},
      {"name": "NodeSprite", "value": 19382},
      {
       "name": "render",
       "children": [
        {"name": "ArrowType", "value": 698},
        {"name": "EdgeRenderer", "value": 5569},
        {"name": "IRenderer", "value": 353},
        {"name": "ShapeRenderer", "value": 2247}
       ]
      },
      {"name": "ScaleBinding", "value": 11275},
      {"name": "Tree", "value": 7147},
      {"name": "TreeBuilder", "value": 9930}
     ]
    },
    {
     "name": "events",
     "children": [
      {"name": "DataEvent", "value": 2313},
      {"name": "SelectionEvent", "value": 1880},
      {"name": "TooltipEvent", "value": 1701},
      {"name": "VisualizationEvent", "value": 1117}
     ]
    },
    {
     "name": "legend",
     "children": [
      {"name": "Legend", "value": 20859},
      {"name": "LegendItem", "value": 4614},
      {"name": "LegendRange", "value": 10530}
     ]
    },
    {
     "name": "operator",
     "children": [
      {
       "name": "distortion",
       "children": [
        {"name": "BifocalDistortion", "value": 4461},
        {"name": "Distortion", "value": 6314},
        {"name": "FisheyeDistortion", "value": 3444}
       ]
      },
      {
       "name": "encoder",
       "children": [
        {"name": "ColorEncoder", "value": 3179},
        {"name": "Encoder", "value": 4060},
        {"name": "PropertyEncoder", "value": 4138},
        {"name": "ShapeEncoder", "value": 1690},
        {"name": "SizeEncoder", "value": 1830}
       ]
      },
      {
       "name": "filter",
       "children": [
        {"name": "FisheyeTreeFilter", "value": 5219},
        {"name": "GraphDistanceFilter", "value": 3165},
        {"name": "VisibilityFilter", "value": 3509}
       ]
      },
      {"name": "IOperator", "value": 1286},
      {
       "name": "label",
       "children": [
        {"name": "Labeler", "value": 9956},
        {"name": "RadialLabeler", "value": 3899},
        {"name": "StackedAreaLabeler", "value": 3202}
       ]
      },
      {
       "name": "layout",
       "children": [
        {"name": "AxisLayout", "value": 6725},
        {"name": "BundledEdgeRouter", "value": 3727},
        {"name": "CircleLayout", "value": 9317},
        {"name": "CirclePackingLayout", "value": 12003},
        {"name": "DendrogramLayout", "value": 4853},
        {"name": "ForceDirectedLayout", "value": 8411},
        {"name": "IcicleTreeLayout", "value": 4864},
        {"name": "IndentedTreeLayout", "value": 3174},
        {"name": "Layout", "value": 7881},
        {"name": "NodeLinkTreeLayout", "value": 12870},
        {"name": "PieLayout", "value": 2728},
        {"name": "RadialTreeLayout", "value": 12348},
        {"name": "RandomLayout", "value": 870},
        {"name": "StackedAreaLayout", "value": 9121},
        {"name": "TreeMapLayout", "value": 9191}
       ]
      },
      {"name": "Operator", "value": 2490},
      {"name": "OperatorList", "value": 5248},
      {"name": "OperatorSequence", "value": 4190},
      {"name": "OperatorSwitch", "value": 2581},
      {"name": "SortOperator", "value": 2023}
     ]
    },
    {"name": "Visualization", "value": 16540}
   ]
  }
 ]
};



function search_and_delete(obj, search_term, parent) {
  if (obj.name === search_term) {
    parent.splice(parent.indexOf(obj), 1);
  } else {
    Object.values(obj).forEach((val) => {
      if (val && typeof val === "object") {
          search_and_delete(val, search_term, obj)
      }
    });
  }
}

search_and_delete(data, "Interpolator") 
console.log(data);

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

6 Comments

This works at depth 1, but not for deeper depths. At deeper depths all children are removed, not just the one you search for.
See second snippet, it looks to work fine for me, can you post an example of the input data for which this doesn't work for you?
Try running your answer on: raw.githubusercontent.com/d3/d3-hierarchy/v1.1.8/test/data/… and do the following example: search_and_delete(data, "Interpolator", data) You will see it removes the correct entry, but also the last entry in that group.
Please see UPDATE in answer
See edit, the for..in was the problem (have to be very careful when mutating while iterating)
|
1

Simple filter implementation that uses breadth first search to eliminate processing children of elements that should be deleted.

data = {
    "name": "root",
    "children": [
       { "name": "bob", "children": [] },
       { "name": "susan" },
       { "name": "chris", "children": [] },
       { "name": "bobo", "children": [] },
       { "name": "heather" },
       { "name": "yoink", "children": 
          [ { "name": "bob", "children": [] },
            { "name": "susan", "children": [] }
          ]
       },
       { "name": "bob", "children": 
          [ { "name": "susan", "children": [] },
            { "name": "chris", "children": [] }
          ]
       },
       { "name": "bob" },
    ]
}

function search_and_delete(obj, search_term) {
  if (obj.name === search_term) {
    return undefined
  }
  if (obj.children) obj.children = obj.children.filter( elem => search_and_delete(elem, search_term) )
  return obj
}

console.log(search_and_delete(data, 'bob'))
console.log(search_and_delete(data, 'root'))

Using your flare data:

async function getData() {
	return $.getJSON('https://raw.githubusercontent.com/d3/d3-hierarchy/v1.1.8/test/data/flare.json', function(result) {
    	return result;
    });
}
$(function() {
	getData().then(function(data) {
		console.log(search_and_delete(data, 'Interpolator'))
	})
});

function search_and_delete(obj, search_term) {
  if (obj.name === search_term) {
    return undefined
  }
  if (obj.children) obj.children = obj.children.filter( elem => search_and_delete(elem, search_term) )
  return obj
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

7 Comments

@Cybernetic You don't say what version of JavaScript you're using. You could try obj.children = Array.prototype.filter(obj.children, ...., but please supply the version
V8 7.5.288.30 Also, please see UPDATE in question.
@Cybernetic no offense, but your question now needs to be clarified. There is no logical reason presented in your question that explains why only those two entries would be removed, nor what the 3rd argument of search_and_delete is for.
@CertainPerformance figured it out perfectly. Thanks for trying.
You need to return obj in a separate line. Currently, it is returning the array obj.children and not an object (I'll delete my answer since this seems better)
|

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.