Matt Baech
Matt Baech

Reputation: 444

Create tree structured JSON

I have a list of objects, of type TDepartment which looks like this

TDepartment = class
  ID : Integer;
  Name : string;
  ParentDepartmentID : Integer;
end;

I need to create a TJSONObject, with an array of departments, which all can also have an array of departments. So the depth of this is unknown.

I am at a point right now where it simply doesn't make sense to me, but I would like the resulting JSON to look like this:

    "department_id": "5",
    "department_name": "100",
    "parent_dept_id": "",
    "subdepartments": [{
        "department_id": "8",
        "department_name": "300",
        "parent_dept_id": "5",
        "subdepartments": [{
            "department_id": "1",
            "department_name": "310",
            "parent_dept_id": "8",
            "subdepartments": []

Keep in mind that each level has unknown amount of siblings and children. I guess i need to write a recursive procedure, but I am unable to visualize it.

Upvotes: 3

Views: 1706

Answers (2)

Dsm
Dsm

Reputation: 6013

I would create a parallel tree structure leaving the original intact. Your current structure is inverted as to what you need, so you scan through your current objects placing them in the tree. But without knowing the current structure this is difficult give sample code, but assuming all departments exist in some sort of list, (let us say called 'Departments') and the 'root' department has a parent department ID of zero it would go something like this:

unit Unit1;

interface

uses
  System.Generics.Collections;

type
  TDepartment = class
    ID : Integer;
    Name : string;
    ParentDepartmentID : Integer;
  end;

  TDepartmentStructure = class
    ID : Integer;
    Name : string;
    ParentDepartmentID : Integer;
    SubDepartments: TList< TDepartmentStructure >;
    constructor Create( const pBasedOn : TDepartment );
  end;

var
  Department : TObjectList<TDepartment>;

function CopyStructure( pDepartment : TList<TDepartment> ) : TDepartmentStructure; // returns root

implementation

var
  DepartmentStructure : TObjectList<TDepartmentStructure>;

function CopyStructure( pDepartment : TList<TDepartment> ) : TDepartmentStructure;
var
  i, j: Integer;
begin
  // stage one - copy everything
  for i := 0 to pDepartment.Count - 1 do
  begin
    DepartmentStructure.Add( TDepartmentStructure.Create( pDepartment[ i ] ));
  end;
  // now go through and build structure
  Result := nil;
  for i := 0 to DepartmentStructure.Count - 1 do
  begin
    if DepartmentStructure[ i ].ID = 0 then
    begin
      // root
      Result := DepartmentStructure[ i ];
    end
    else
    begin
      for j := 0 to DepartmentStructure.Count - 1 do
      begin
        if DepartmentStructure[ i ].ParentDepartmentID = DepartmentStructure[ j ].ID then
        begin
          DepartmentStructure[ j ].SubDepartments.Add( DepartmentStructure[ i ] );
          break;
        end;
      end;
    end;
  end;
end;

{ TDepartmentStructure }

constructor TDepartmentStructure.Create(const pBasedOn: TDepartment);
begin
  inherited Create;
  ID := pBasedOn.ID;
  Name := pBasedOn.Name;
  ParentDepartmentID := pBasedOn.ParentDepartmentID;
  SubDepartments:= TObjectList< TDepartmentStructure >.Create( FALSE ); // we do NOT own these objects!

end;

initialization
  DepartmentStructure := TObjectList<TDepartmentStructure>.Create( TRUE );

finalization
  DepartmentStructure.Free;

end.

Note that this is for illustration purposes only. You would probably not create and destroy the structures where I have. Once you have the structure you can create your JSON records using your current routines no doubt.

Upvotes: 0

DNR
DNR

Reputation: 1659

First, you probably want your declaration of TDepartmentto match the nested structure you describe:

TDepartment = class
  ID : Integer;
  Name : string;
  ParentDepartmentID : Integer;
  SubDepartments: array of TDepartment;
end;

In order to serialize this I would recommend using the SuperObject library rather than the inbuilt JSON classes:

function TDepartment.Serialize: ISuperObject;
  var Context: TSuperRttiContext;
begin
  Context := TSuperRttiContext.Create;
  try
    Result := Context.AsJson<TDepartment>(self);
  finally
    Context.Free;
  end;
end;

In the comments, OP mentioned that TDepartment contains a lot more fields, but only the ones in the question should be serialized; also TJSONObject has to be used, and a department does not know about its children. You could do something like that:

function TDepartment.Serialize2(AllDepartments: TList<TDepartment>): TJSONObject;
  var Department: TDepartment;
      Subdepartments: TJSONArray;
begin
  Result := TJSONObject.Create;
  Result.AddPair(TJSONPair.Create('department_id', TJSONNumber.Create(ID)));
  Result.AddPair(TJSONPair.Create('department_name', Name));
  Result.AddPair(TJSONPair.Create('parent_dept_id', TJSONNumber.Create(ParentDepartmentID)));

  Subdepartments := TJSonArray.Create;
  for Department in AllDepartments do
  begin
    if (Department.ParentDepartmentID <> ID) then Continue;
    Subdepartments.AddElement(Department.Serialize2(AllDepartments));
  end;
  Result.AddPair(TJSONPair.Create('subdepartments', Subdepartments));
end;

Upvotes: 2

Related Questions