1

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.

pragma
  • 13
  • 4
  • is all your json file the same ? (not the value, but the name of the parameters and the disposition) – Rayane Staszewski Sep 23 '22 at 16:06
  • Consider updating the example code to make it compile. For example you probably need `using System.Collections;` – Mark Schultheiss Sep 23 '22 at 16:15
  • The top level element is a `childObjects` array with only one element. Since you say names are unique, traverse the tree and store each `childObjects` element in a dictionary, where the key is the name. Afterwards, iterate the dictionary and distribute items. No need for recursion. – BurnsBA Sep 23 '22 at 18:21
  • I would add events and delegates on the class to report back to the (grant)parent – Aldert Sep 23 '22 at 18:59
  • @RayaneStaszewski Yes, all the names of parameters / structure of the JSON is the same throughout – pragma Sep 23 '22 at 20:09
  • @MarkSchultheiss Thanks. In visual studio it says "using directive is unnecessary" and is compiling for me. I have added it to the example anyway for clarity. – pragma Sep 23 '22 at 20:16
  • @BurnsBA Am I right in saying we still need to use recursion to traverse the tree? As we don't know how many levels of nesting there will be. But then afterwards, once we have the dictionary populated, we can just loop through it? – pragma Sep 23 '22 at 20:18
  • You will need to visit every node on the tree at least once. You can do that recursively, or create a queue/stack and process nodes that way. https://stackoverflow.com/a/159777/1462295 – BurnsBA Sep 23 '22 at 20:33
  • @pragma okay, so is a possible solution for you to deserialize the json, modify it with c# objets and serialize the result ? If yes I can make the class for you. – Rayane Staszewski Sep 24 '22 at 13:21
  • @RayaneStaszewski Yes please, that is what I am trying to do. – pragma Sep 24 '22 at 14:46

2 Answers2

0

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);
  • Hello thanks, I like this website too it's very useful. – pragma Sep 25 '22 at 10:33
  • This example shows me how to deserialise and serialise back, which is good but it doesn't show me how to do the recursion part? I need to set properties on the objects in the hierarchy based on some properties defined in the JSON that is deserialised. Do you know how the code would look for that too? @Rayane Staszewski – pragma Sep 25 '22 at 10:35
0

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);
    }
}
Mark Schultheiss
  • 32,614
  • 12
  • 69
  • 100