Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor? (https://www.delphipraxis.net/182125-erste-schritte-mit-rest-json-pas-wann-braucht-man-einen-eigenen-tjsoninterceptor.html)

Der schöne Günther 2. Okt 2014 11:13

Delphi-Version: 5

Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor?
 
Da bin ich wieder. Es werden noch viele Fragen zu diesem Thema kommen da leider selbst in XE7 noch alles vollkommen undokumentiert ist.

Es tut mir leid um den vielen Code, ich habe extra das vorgestern erschienene "Delphi cookbook" geholt. Es bietet ein Beispiel zu genau meiner Frage, aber leider ebenfalls undokumentiert. Deshalb kann ich es fast nicht verstehen. :-(


Nehmen wir an, ich habe folgende zwei Typen:
Delphi-Quellcode:
TInnerType = class(TInterfacedObject, IInterface)
   public var
      [JSonName('someValue')]
      someValue: Single;
   public
      constructor Create(); // Setzt someValue auf 42.99
end;

TOuterType = class
   protected var
      [JSonName('someSimpleField')]
      someSimpleField: Integer;
      [JSonName('someAdvancedField')]
      someAdvancedField: IInterface;
   public
      constructor Create(); // Setzt someSimpleField auf 42 und
      // someAdvancedField auf eine neue TInnerType-Instanz
end;
Wandele ich eine TOuterType-Instanz in JSON um, ist die Ausgabe
Code:
{"someSimpleField":42}
. Das Feld "someAdvancedField" fehlt völlig.

Im Beispiel von Daniele Tetis Buch serialisiert er eine Klasse TPhoto nach JSON. Diese Klasse enthält einen TStream. Damit dieser TStream serialisiert wird hängt er ein Attribut
Delphi-Quellcode:
[JSONReflect(ctString, rtString, TStreamInterceptor)]
an das Feld. Den
Delphi-Quellcode:
TStreamInterceptor
hat er dann selber geschrieben.


Was ich dann tat: Ich fühlte mich schlau und fügte meinem Feld "someAdvancedField" ebenfalls folgendes Attribut hinzu:
Delphi-Quellcode:
TOuterType = class
   protected var
      [JSonName('someSimpleField')]
      someSimpleField: Integer;
      [JSonName('someAdvancedField')] [JSonReflect(ctObjects, rtObject, TJSONInterceptor )]
      someAdvancedField: IInterface;
   public
      constructor Create();
end;
Als Ergebnis gibt es:
Code:
{"someSimpleField":42,"someAdvancedField":[]}
Ich hatte das Gefühl schon fast da zu sein. Aber ein Blick auf die Implementation von
Delphi-Quellcode:
Rest.JSonReflect.TJSONInterceptor
bringt nur eine Klasse mit leeren Methoden zu Tage. Muss ich jetzt wirklich für jeden Firlefanz einen eigenen Interceptor schreiben? Oder gehe ich hier grade einen völlig falschen Weg?

Ich versuche meine Nerverei so klein wie möglich zu halten, aber mir fehlt irgendwie das Grundverständnis wie die Embarcadero-Serialisierung vonstatten gehen soll: Die letzten Embarcadero-Videos zu JSON behandeln nur trivialste Dinge wie das Anzeigen des JSON-Strings in Memos. Dokumentation scheint überhaupt keine vorhanden. Ich weiß noch nicht einmal, was der Unterschied zwischen dem Kram aus Rest.Json.pas und Data.DBXJSONReflect.pas ist. Das Posten des Beispiels aus dem Buch habe ich bewusst vermieden, Copyright und so.

Uwe Raabe 2. Okt 2014 11:17

AW: Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor
 
So auf Anhieb würde ich sagen, daß ein IInterface nicht gerade viele Felder zum Serialisieren bereit stellt.

Der schöne Günther 2. Okt 2014 11:20

AW: Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor
 
Das stimmt. Aber er muss es doch nur auf
Delphi-Quellcode:
TObject
casten und schon kann er es nach Feldern absuchen wie er es sonst auch macht.

Ich kann ja ebenso sagen
Delphi-Quellcode:
var
   interfacedObj: IInterface;
begin
   interfacedObj := TInnerType.Create();
   marshalledObject := TJson.ObjectToJsonObject( interfacedObj as TObject );

   WriteLn( marshalledObject.ToJSON() );
end.
und bekomme
Code:
{"someValue":42.99}

mjustin 2. Okt 2014 12:08

AW: Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1274590)
Das stimmt. Aber er muss es doch nur auf
Delphi-Quellcode:
TObject
casten und schon kann er es nach Feldern absuchen wie er es sonst auch macht.

Wobei mir persönlich - wenn ich der Derialisierer wäre - dann im JSON noch der konkrete Typ des serialisierten Objektes (also der Klassenname), fehlt. Denn ich kann mir keinen Deserialisierer vorstellen, der zu den im JSON enthaltenen Attributen automatisch alle im System (oder einer Klassenliste) bekannten Typen absucht und die passendste Klasse wählt.

Der schöne Günther 2. Okt 2014 12:18

AW: Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor
 
Sagen wir mal so: Wenn ich nur mit Gewalt das "as TObject" reindrücke scheint alles zu funktionieren. Zumindest glaube ich das. Denn das grundlegende Verständnis fehlt mir weiterhin. Warum war das jetzt überhaupt nötig?

Interface-Interceptor über Attribut angehangen:
Delphi-Quellcode:
   TOuterType = class
      protected var
         [JSonName('someInt')]
         someInt: Integer;

         [JSonName('someIntf')] [JSONReflect(ctObject, rtObject, Unit2.TJSONInterfaceInterceptor)]
         someIntf: IInterface;

      public
         constructor Create();
   end;
Interceptor sieht so aus:

Delphi-Quellcode:
unit Unit2;

interface uses Rest.JSonReflect;

type
   TJSONInterfaceInterceptor = class(Rest.JsonReflect.TJSONInterceptor)
      protected
         function getAsIInterface(const Data: TObject; const Field: string): IInterface;
      public
         function ObjectConverter(Data: TObject; Field: string): TObject; override;
   end experimental;

implementation uses System.Rtti, Rest.Json;

{ TJSONInterfaceInterceptor }

function TJSONInterfaceInterceptor.getAsIInterface(const Data: TObject; const Field: string): IInterface;
var
   rttiType: TRttiType;
   rttiField: TRttiField;
   rttiContext: TRttiContext;
begin
   rttiType := rttiContext.GetType(Data.ClassType);
   rttiField := rttiType.GetField(Field);
   Result := rttiField.GetValue(Data).AsInterface;
end;

function TJSONInterfaceInterceptor.ObjectConverter(Data: TObject; Field: string): TObject;
begin
   Result := getAsIInterface(Data, Field) as TObject;
end;

initialization
   TJSONInterfaceInterceptor.ClassName();
end.

Erhaltene Ausgabe:
Code:
{"someSimpleField":42,"someAdvancedField":{"someValue":42.25}}

Uwe Raabe 2. Okt 2014 12:41

AW: Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1274610)
Sagen wir mal so: Wenn ich nur mit Gewalt das "as TObject" reindrücke scheint alles zu funktionieren.

Diese "Gewalt" würde ich vom Serialisierer aber gar nicht erwarten. Ein Interface ist nun mal nur ein Pointer auf eine Schnittstelle und nicht auf eine Object-Instanz. Beim Deserialisieren müsste diese Instanz ja auch wieder erzeugt werden. Was würde dann aber passieren, wenn du mehrere Interface-Felder hast, die auf dieselbe Objektinstanz verweisen? Woher soll der Deserialisierer denn wissen, daß er dafür nur eine Instanz erzeugen darf?

Der schöne Günther 2. Okt 2014 12:45

AW: Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor
 
Du hast, wie immer, Recht. Das würde ich auch gar nicht erwarten, das ist eigentlich Aufgabe des Entwicklers.

Ich bin nur verwirrt, dass diese paar Zeilen Extra-Arbeit für etwas allgemeingültiges nötig waren. Nicht, dass es hier doch einen Standard-Weg gibt, und ich kenne ihn nur nicht?

Und ob ich mich nur einbilde, es funktioniere.

Uwe Raabe 2. Okt 2014 12:53

AW: Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor
 
Etwas Licht sollte ein Blick in die Methode TTypeMarshaller<TSerial>.MarshalSimpleField in Data.DBXJSONReflect.pas bringen.

Der schöne Günther 2. Okt 2014 13:03

AW: Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor
 
Danke, da bin ich schon durchgeschwommen. Ich würde im Aufruf noch eins höher auf
Delphi-Quellcode:
TTypeMarshaller<TSerial>.MarshalData(Data: TObject);
gehen. Die ist nicht nur (trotz der Größe) gut lesbar, sondern hat auch Dokumentation (die es nicht ins Docwiki geschafft hat):

Zitat:

If no user converters are defined, it tries to use the default
ones. If a type converter exists then that one is used. If a field converter
is used that the field converter is used. The field converter has precedence
over the type one.

Auch: Kommentar aus DbxJsonReflect.pas:
Zitat:

/// These are the fields for which there is already a built-in
/// conversion/reversion: integer, string, char, enumeration, float, object. For
/// these types the field values are ignored and user conversion is expected:
/// set, method, variant, interface, pointer, dynArray, classRef, array.

Produktiv habe ich heute bislang fast nichts geschafft, aber immerhin einiges gelernt :-)


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:02 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz