Home > Software design >  How to recursively traverse down a nested object with n levels and refer back to any grandparent lev
How to recursively traverse down a nested object with n levels and refer back to any grandparent lev

Time:09-27

I need to recurse down an object with arbitrarily deep nesting of child objects. On each level of nesting I reach, I need to refer back to any (potentially all) of the objects on levels I've already been through, i.e. any grandparents of the current child object I am at. I then need to set properties on any of those referenced parent objects based on the current object's state. The properties I'm setting is determined by JSON.

Some values will be determined at runtime (those that are randomly generated in the example below) and others will just be strings from the JSON.

Minimum reproducible example:

Take this JSON as input:

{
    "name": "foo",
    "targetChildren": [
        "baz",
        "quux"
    ],
    "valsFromChildren": [],
    "runtimeValsFromChildren": [],
    "childObjects": [
        {
            "name": "baz",
            "valToGive": "giftFromBaz",
            "targetChildren": [
                "qux"
            ],
            "valsFromChildren": [],
            "runtimeValsFromChildren": [],
            "childObjects": [
                {
                    "name": "qux",
                    "valToGive": "giftFromQux"
                },
                {
                    "name": "quux",
                    "valToGive": "giftFromQuux"
                }
            ]
        }
    ]
}

The targetChildren refer to the names of the child/grandchild objects from which I want to get values and give them to "this" object. The valsFromChildren List should be populated based on that connection - the children with the matching names from targetChildren should put their valToGive value in the valsFromChildren List on the object targeting them. We can assume that there will be no duplicate names.

runtimeValsFromChildren is a List that gets filled with random numbers that are computed by the child object that is giving the value to the parent.

C# console app (needs to be Dotnet version 6):

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Collections;

var json = "{\"name\":\"foo\",\"targetChildren\":[\"baz\",\"quux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"baz\",\"valToGive\":\"giftFromBaz\",\"targetChildren\":[\"qux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"qux\",\"valToGive\":\"giftFromQux\"},{\"name\":\"quux\",\"valToGive\":\"giftFromQuux\"}]}]}";
var obj = JsonSerializer.Deserialize<Obj>(json);
DoRecursion(obj);
var newObjJson = JsonSerializer.Serialize(obj);

static Obj DoRecursion(Obj obj)
{
    if (obj.ChildObjects == null || obj.ChildObjects.Count <= 0)
        return obj;

    foreach (var child in obj.ChildObjects)
    {
        var parent = obj;
        if (parent.TargetChildren != null && parent.TargetChildren.Contains(child.Name))
        {
            // Give the values to the parent that is targeting them.
            parent.ValsFromChildren.Add(child.ValToGive);
            parent.RuntimeValsFromChildren.Add(child.RuntimeVal);
        }
        return DoRecursion(child);
    }
    return obj;
}

class Obj
{
    [JsonPropertyName("name")]
    public string Name { get; set; }

    [JsonPropertyName("valToGive")]
    public string ValToGive { get; set; }

    [JsonPropertyName("targetChildren")]
    public List<string> TargetChildren { get; set; }

    [JsonPropertyName("valsFromChildren")]
    public List<string> ValsFromChildren { get; set; }

    [JsonPropertyName("runtimeValsFromChildren")]
    public List<int> RuntimeValsFromChildren { get; set; }

    [JsonPropertyName("childObjects")]
    public List<Obj> ChildObjects { get; set; }

    [JsonIgnore]
    public int RuntimeVal => new Random().Next(0, 100);
}

Desired output in JSON:

{
    "name": "foo",
    "targetChildren": [
        "baz",
        "quux"
    ],
    "valsFromChildren": [
        "giftFromBaz",
        "giftFromQuux"
    ],
    "runtimeValsFromChildren": [
        31,
        88
    ],
    "childObjects": [
        {
            "name": "baz",
            "valToGive": "giftFromBaz",
            "targetChildren": [
                "qux"
            ],
            "valsFromChildren": [
                "giftFromQux"
            ],
            "runtimeValsFromChildren": [
                43
            ],
            "childObjects": [
                {
                    "name": "qux",
                    "valToGive": "giftFromQux"
                },
                {
                    "name": "quux",
                    "valToGive": "giftFromQuux"
                }
            ]
        }
    ]
}

Actual output:

{
    "name": "foo",
    "targetChildren": [
        "baz",
        "quux"
    ],
    "valsFromChildren": [
        "giftFromBaz"
    ],
    "runtimeValsFromChildren": [
        43
    ],
    "childObjects": [
        {
            "name": "baz",
            "valToGive": "giftFromBaz",
            "targetChildren": [
                "qux"
            ],
            "valsFromChildren": [
                "giftFromQux"
            ],
            "runtimeValsFromChildren": [
                60
            ],
            "childObjects": [
                {
                    "name": "qux",
                    "valToGive": "giftFromQux"
                },
                {
                    "name": "quux",
                    "valToGive": "giftFromQuux"
                }
            ]
        }
    ]
}

I need the valsFromChildren List on the object with name foo to include giftFromQuux (a grand child that I want to grab from the 3rd level down). At the moment it only manages to get the value from its immediate child ("baz"), not its grand child ("quux"). It needs to be a recursive solution that should work for grandparents n levels down the nesting.

I would also like to know how to not mutate the original object but instead return a copy at a different memory address, i.e. have the method not have side effects.

Thanks.

CodePudding user response:

Okay so here is your c# class (I used this website)

public class ChildObject
{
    public string name { get; set; }
    public string valToGive { get; set; }
    public List<string> targetChildren { get; set; }
    public List<string> valsFromChildren { get; set; }
    public List<int> runtimeValsFromChildren { get; set; }
    public List<ChildObject> childObjects { get; set; }
}

public class Root
{
    public string name { get; set; }
    public List<string> targetChildren { get; set; }
    public List<string> valsFromChildren { get; set; }
    public List<int> runtimeValsFromChildren { get; set; }
    public List<ChildObject> childObjects { get; set; }
}

So you just have to do :

Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(theJsonString);

And then when you finish modifying thing do:

string myNewJson = JsonConvert.SerializeObject(myDeserializedClass);

CodePudding user response:

Frankly it is not totally clear on your intent/desired result here SO here is a console app that walks the deserialized and then the altered object a few ways. Perhaps you can build from this a bit. This is a bit "chatty" to give you some ideas where you can go with this.

Here is a working copy of this sample: https://dotnetfiddle.net/qakv6n

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World");
        var json = "{\"name\":\"foo\",\"targetChildren\":[\"baz\",\"quux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"baz\",\"valToGive\":\"giftFromBaz\",\"targetChildren\":[\"qux\"],\"valsFromChildren\":[],\"runtimeValsFromChildren\":[],\"childObjects\":[{\"name\":\"qux\",\"valToGive\":\"giftFromQux\"},{\"name\":\"quux\",\"valToGive\":\"giftFromQuux\"}]}]}";
        var obj = JsonSerializer.Deserialize<JsonObj>(json);
        Console.WriteLine($@"Hello World {obj.Name} has {obj.TargetChildren.Count} targets");
        foreach (var item in obj.TargetChildren.Select((value, i) => new { i, value }))
        {
            Console.WriteLine($@"Hello item: {item} has targets");
            var value = item.value;
            var index = item.i;
            Console.WriteLine($@"x: index:{index} val: {value} ");
        }

        DoRecursion(obj);
        var newObjJson = JsonSerializer.Serialize(obj);
            Console.WriteLine($@"serial {newObjJson} ");
        foreach (var rv in obj.RuntimeValsFromChildren)
        {
            Console.WriteLine($@"Count {rv} ");
        }

        foreach (var v in obj.ValsFromChildren)
        {
            Console.WriteLine($@"Vals {v} ");
        }
    }

    static JsonObj DoRecursion(JsonObj obj)
    {
        if (obj.ChildObjects == null || obj.ChildObjects.Count <= 0)
            return obj;
        foreach (var child in obj.ChildObjects)
        {
            Console.WriteLine($@"Hello Recursing  Name:{child.Name} Count: {obj.ChildObjects.Count} kiddos");
            var g = obj.ChildObjects.Where(co => co.Name == child.Name)
                .Select(x => new{Name= x.Name, Val= x.ValToGive}).First();
            Console.WriteLine($"g:{g.Name} v:{g.Val}");
            var newObjJson = JsonSerializer.Serialize(obj);
            var parent = obj;
            if (parent.TargetChildren != null && parent.TargetChildren.Contains(child.Name))
            {
                // Give the values to the parent that is targeting them.
                parent.ValsFromChildren.Add(child.ValToGive);
                parent.RuntimeValsFromChildren.Add(child.RuntimeVal);
            }

            return DoRecursion(child);
        }

        return obj;
    }

    class JsonObj
    {
        [JsonPropertyName("name")]
        public string Name { get; set; }

        [JsonPropertyName("valToGive")]
        public string ValToGive { get; set; }

        [JsonPropertyName("targetChildren")]
        public List<string> TargetChildren { get; set; }

        [JsonPropertyName("valsFromChildren")]
        public List<string> ValsFromChildren { get; set; }

        [JsonPropertyName("runtimeValsFromChildren")]
        public List<int> RuntimeValsFromChildren { get; set; }

        [JsonPropertyName("childObjects")]
        public List<JsonObj> ChildObjects { get; set; }

        [JsonIgnore]
        public int RuntimeVal => new Random().Next(0, 100);
    }
}
  • Related