AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Erste Schritte mit Rest.Json.pas: Wann braucht man einen eigenen TJsonInterceptor?
Thema durchsuchen
Ansicht
Themen-Optionen

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

Ein Thema von Der schöne Günther · begonnen am 2. Okt 2014 · letzter Beitrag vom 2. Okt 2014
Antwort Antwort
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#1

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

  Alt 2. Okt 2014, 11:13
Delphi-Version: 5
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 [JSONReflect(ctString, rtString, TStreamInterceptor)] an das Feld. Den 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 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.
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.006 Beiträge
 
Delphi 12 Athens
 
#2

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

  Alt 2. Okt 2014, 11:17
So auf Anhieb würde ich sagen, daß ein IInterface nicht gerade viele Felder zum Serialisieren bereit stellt.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

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

  Alt 2. Okt 2014, 11:20
Das stimmt. Aber er muss es doch nur auf 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}

Geändert von Der schöne Günther ( 2. Okt 2014 um 11:21 Uhr) Grund: Fehler im Code korrigiert
  Mit Zitat antworten Zitat
mjustin

Registriert seit: 14. Apr 2008
3.004 Beiträge
 
Delphi 2009 Professional
 
#4

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

  Alt 2. Okt 2014, 12:08
Das stimmt. Aber er muss es doch nur auf 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.
Michael Justin
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#5

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

  Alt 2. Okt 2014, 12:18
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}}
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.006 Beiträge
 
Delphi 12 Athens
 
#6

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

  Alt 2. Okt 2014, 12:41
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?
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#7

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

  Alt 2. Okt 2014, 12:45
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.
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.006 Beiträge
 
Delphi 12 Athens
 
#8

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

  Alt 2. Okt 2014, 12:53
Etwas Licht sollte ein Blick in die Methode TTypeMarshaller<TSerial>.MarshalSimpleField in Data.DBXJSONReflect.pas bringen.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.110 Beiträge
 
Delphi 10 Seattle Enterprise
 
#9

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

  Alt 2. Okt 2014, 13:03
Danke, da bin ich schon durchgeschwommen. Ich würde im Aufruf noch eins höher auf 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

Geändert von Der schöne Günther ( 2. Okt 2014 um 13:05 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:22 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz