Set Level to a nested list with many layers

[{
    "EmployeeID": 1,
    "EmployeeName": "ABC",
    "Department": "US",
    "Level": null,
    "ParentID": null,
    "Employees": [{
            "EmployeeID": 4,
            "EmployeeName": "ABCD",
            "Department": "US",
            "Level": null,
            "ParentID": 1,
            "Employees": []
        },
        {
            "EmployeeID": 6,
            "EmployeeName": "ABCDS",
            "Department": "US",
            "Level": null,
            "ParentID": 1,
            "Employees": []
        },
        {
            "EmployeeID": 7,
            "EmployeeName": "ABCDS",
            "Department": "US",
            "Level": null,
            "ParentID": 1,
            "Employees": [{
                    "EmployeeID": 8,
                    "EmployeeName": "ABCD",
                    "Department": "US",
                    "Level": null,
                    "ParentID": 7,
                    "Employees": []
                },
                {
                    "EmployeeID": 9,
                    "EmployeeName": "ABCDS",
                    "Department": "US",
                    "Level": null,
                    "ParentID": 7,
                    "Employees": []
                }
            ]
        }
    ]
}]

The above is a JSON nested list, after serializing it into a C# List object I want to set Level prop so that, for example Employee ID 1 will have level 1, Employee ID 4, 6, 7 will have level 2, Employee ID 8, 9 will have level 3.

This is a nested list it can have n number of layers, I have tried few things by looping but failing, can any one please help. Below is my class object.

public class Employee
{
    public int EmployeeID { get; set; }
    public string EmployeeName { get; set; }
    public string Department { get; set; }
    public int? Level { get; set; }
    public List<Employee> Employees { get; set; }
}

Converting it to List object using below code

var jsonstring = File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "Employee.json"));
var employeeList = JsonSerializer.Deserialize<List<Employee>>(jsonstring);

Answer

This is a perfect use case for recursive programming. However to avoid using the call-stack (which is limited in size), we can use a custom stack which is technically limited only by the system memory. You can do something like this:

public static class EmployeeExtensions {
     public static Employee PopulateLevels(this Employee e){
         var employees = new Stack<Employee>();
         employees.Push(e);
         if(e.Level == null){
             e.Level = 1;
         }
         while(employees.Count > 0){
            var parent = employees.Pop();
            foreach(var child in parent.Employees ?? Enumerable.Empty<Employee>()){
                child.Level = parent.Level + 1;
                employees.Push(child);
            }
         }
         return e;
     }
}

Use it:

foreach(var e in employeeList){
     e.PopulateLevels();
}

//or inline with the ToList
var employeeList = JsonSerializer.Deserialize<List<Employee>>(jsonstring)
                                 .Select(e => e.PopulateLevels()).ToList();

I would not care too much about performance if the data size is small (or even fairly large), the code above however may be improved a bit like this:

public static class EmployeeExtensions {
     public static Employee PopulateLevels(this Employee e){
         if(e.Level == null){
             e.Level = 1;
         }
         if(e.Employees == null) return e;

         var employees = new Stack<Employee>();
         employees.Push(e);             
         while(employees.Count > 0){
            var parent = employees.Pop();
            var childLevel = parent.Level + 1;
            foreach(var child in parent.Employees){
                child.Level = childLevel;
                if(child.Employees?.Any() ?? false){
                  employees.Push(child);
                }
            }
         }
         return e;
     }
}

Of course benchmarking is always needed to see how much it can be improved. Minor improvement is usually not worth the effort.

To flatten the employees:

public static class EmployeeExtensions {
    public static IEnumerable<Employee> GetAllEmployees(this Employee e){
         yield return e;
         if(e.Employees == null) yield break;

         var employees = new Stack<Employee>();
         employees.Push(e);             
         while(employees.Count > 0){
              var parent = employees.Pop();
              foreach(var child in parent.Employees){
                  yield return child;
                  if(child.Employees?.Any() ?? false){
                    employees.Push(child);
                  }
              }
         }
    }
}

Use it

var employeeList = employeeList.SelectMany(e => e.GetAllEmployees()).ToList();