JSON Deserialisierung mit TObjectList

Für alles, was in den übrigen Lazarusthemen keinen Platz, aber mit Lazarus zutun hat.
Antworten
jus
Beiträge: 47
Registriert: Fr 6. Mai 2011, 13:29

JSON Deserialisierung mit TObjectList

Beitrag von jus »

Hallo,
ich beschäftige mich aktuell mit Serialisierung von Objekten mit JSON. Ich habe folgendes Objekt, das weitere Unterebenen der selben Objektklasse enthalten kann, die in einer TObjectList enthalten sind. In diesem Fall können die Unterobjekte in Items bzw. FItems gespeichert werden.

Code: Alles auswählen

TMyObject = class(TPersistent)
  private
    FTestProp1: String;
    FTestProp2: Integer;
    FName: WideString;
    FItems: TObjectList;
  public
    constructor Create;
    destructor Destroy; override;
  published
    property Items: TObjectList read FItems write FItems;
    property Name: WideString read fName write fName;
    property TestProp1: String read FTestProp1 write FTestProp1;
    property TestProp2: Integer read FTestProp2 write FTestProp2;
  end;         
Das Objekt wird wie folgt aufgebaut und serialisiert:

Code: Alles auswählen

uses  fpjsonrtti, Contnrs;        
procedure TForm1.Button1Click(Sender: TObject);
var
  MyObject1: TMyObject;
  MyObject1_1: TMyObject;
  MyObject1_2: TMyObject;
  MyObject1_1_1: TMyObject;
  jsoSerialize: TJSONStreamer;
  ts: TStringStream;
  s: String;
begin
  MyObject1 := TMyObject.Create;
  MyObject1_1 := TMyObject.Create;
  MyObject1_2 := TMyObject.Create;
  MyObject1_1_1 := TMyObject.Create;
  jsoSerialize := TJSONStreamer.Create(nil);
  try
    MyObject1.Name := 'Max';
    MyObject1.TestProp1 := 'Ebene 1';
    MyObject1.TestProp2 := 1;

    MyObject1_1.Name := 'Moritz';
    MyObject1_1.TestProp1 := 'Ebene 1_1';
    MyObject1_1.TestProp2 := 11;
    MyObject1.Items.Add(MyObject1_1);

    MyObject1_2.Name := 'Hans';
    MyObject1_2.TestProp1 := 'Ebene 1_2';
    MyObject1_2.TestProp2 := 12;
    MyObject1.Items.Add(MyObject1_2);

    MyObject1_1_1.Name := 'Peter';
    MyObject1_1_1.TestProp1 := 'Ebene 1_1_1';
    MyObject1_1_1.TestProp2 := 111;
    MyObject1_1.Items.Add(MyObject1_1_1);

    s := jsoSerialize.ObjectToJSONString(MyObject1);
    ts := TStringStream.Create(s);
    try
      ts.SaveToFile('Test.json');
    finally
      ts.Free;
    end;

    Memo1.Lines.Text := s;
  finally
    jsoSerialize.Free;
    MyObject1.Free;
  end;
end;
Erstaunlicher Weise werden sogar die Unterebenen richtig in JSON serialisiert. Doch das Deserialisieren will nicht so klappen:

Code: Alles auswählen

procedure TForm1.Button2Click(Sender: TObject);
var
  MyObject: TMyObject;
  jsoDeSerialize: TJSONDeStreamer;
  ts: TStringStream;
  s: String;
  i: Integer;
begin
  ts := TStringStream.Create;
  MyObject := TMyObject.Create;
  jsoDeSerialize := TJSONDeStreamer.Create(nil);
  try
    ts.LoadFromFile('Test.json');

    s := ts.DataString;
    jsoDeSerialize.JSONToObject(s, MyObject);

    WriteToMemo(MyObject);
  finally
    jsoDeSerialize.Free;
    MyObject.Free;
    ts.Free;
  end;
end;  
Es wird nur die oberste Ebene deserialisiert.

Hat einer eine Idee? Habe mal das komplette Projekt angehängt.

lg,
jus
Dateianhänge
SerializeJSONDemo.zip
(126.36 KiB) 34-mal heruntergeladen

PascalDragon
Beiträge: 222
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: JSON Deserialisierung mit TObjectList

Beitrag von PascalDragon »

Ich hab's nicht ausprobiert, aber wenn ich den Code von TJSONDeStreamer richtig einschätze, dann musst du OnGetObject behandeln, da nur über dieses die Klasseninstanz für Kindproperties erzeugt wird.
FPC Compiler Entwickler

jus
Beiträge: 47
Registriert: Fr 6. Mai 2011, 13:29

Re: JSON Deserialisierung mit TObjectList

Beitrag von jus »

PascalDragon hat geschrieben:
Sa 20. Jun 2020, 18:33
Ich hab's nicht ausprobiert, aber wenn ich den Code von TJSONDeStreamer richtig einschätze, dann musst du OnGetObject behandeln, da nur über dieses die Klasseninstanz für Kindproperties erzeugt wird.
ok, vielen Dank für die Hinweis mit dem Event. Ich habs nun aber mit dem "OnRestoreProperty" Event gelöst. Nun werden auch die verschachtelten Unterebenen richtig ausgelesen. :D

Code: Alles auswählen

TForm1 = class(TForm)
//...
  private
//...
    procedure OnRestoreProperty(Sender: TObject; AObject: TObject; Info: PPropInfo; AValue: TJSONData; var Handled: Boolean);
 //...
   end;
implementation
//...
procedure TForm1.Button2Click(Sender: TObject);
var
  MyObject: TMyObject;
  jsoDeSerialize: TJSONDeStreamer;
  ts: TStringStream;
  s: String;
  i: Integer;
begin
  ts := TStringStream.Create;
  MyObject := TMyObject.Create;
  jsoDeSerialize := TJSONDeStreamer.Create(nil);
  try
    jsoDeSerialize.OnRestoreProperty:=@OnRestoreProperty;
    ts.LoadFromFile('Test.json');

    s := ts.DataString;
    jsoDeSerialize.JSONToObject(s, MyObject);

    WriteToMemo(MyObject);
  finally
    jsoDeSerialize.Free;
    MyObject.Free;
    ts.Free;
  end;
end;        

procedure TForm1.OnRestoreProperty(Sender: TObject; AObject: TObject;
  Info: PPropInfo; AValue: TJSONData; var Handled: Boolean);
var
  O: TObject;
  items: TObjectList;
  item: TMyObject;
  I : integer;
  A : TJSONArray;
begin
  Handled := False;
  O := nil;
  if Info^.PropType^.Kind = tkClass then O := GetObjectProp(AObject, Info);
  if (Info^.Name = 'Items') and (AValue.JSONType=jtArray) and (o <> nil) and (o is TObjectList) then
  begin
    items := o as TObjectList;
    A:=AValue As TJSONArray;
    For I:=0 to A.Count-1 do
    begin
      item := TMyObject.Create;
      (Sender as TJSONDeStreamer).JSONToObject(A.Objects[i],item);
      items.Add(item);
    end;
    Handled := True;
  end;
end;        
So wie ich es verstanden habe, kann Deserialisierungsklasse "TJSONDeStreamer" nur TCollection und TStrings deserialisieren. Für was anderes muß man es selber im Event implementieren.

Für den Fall, dass jemand auch mal sowas benötigt, habe ich anbei das aktualisierte Projekt angehängt.

lg,
jus
Dateianhänge
SerializeJSONTest3.zip
(126.63 KiB) 38-mal heruntergeladen

Antworten