Seit Embarcadero Tokyo 10.2 veröffentlicht hat, steht uns Entwicklern ein neuer sehr gut zu gebrauchender JSON Marshaller zur Verfügung. Leider ist die Dokumentation dürftig. Das schöne ist aber, das Embarcadero sich ziemlich an gängige Verfahren hält. So konnte ich z.B. sehr einfach herausfinden was TJsonCustomCreationConverter<T> macht, nachdem ich C# Beispiele analysierte. Da es leider keine offizielle Dokumentation zum TJsonSerializer gibt, beruhen meine Beispiele auf Versuch und Irrtum.
Es gibt 3 Namespaces, welche relevant sind. Zum einen "System.JSON.Serializers", für den eigentlichen Serialisierer und die Klassenattribute, zum anderen "System.JSON.Converters", für eigene Konverter und zuletzt "System.JSON.Types", für z.B. Datumsformate oder Formatierungen.
Diese Klasse ist dafür zuständig, ein Objekt in JSON (Serialisierung) zu formatieren und ein Objekt durch Lesen bzw. Interpretieren einer JSON-Struktur (Deserialisierung) zu erstellen. Hier mal mein Wrapper:
Jetzt aber das Wichtigste, wie nutze ich das ganze um nicht immer alles neu schreiben zu müssen. Bisher hatte ich immer das Problem, dass die Konvertierung in bzw. von JSON immer wieder dem gleichen Muster folgte. In anderen Sprachen war dies schon lange mit Klassenattributen durchzuführen. Nur in Delphi suchte ich nach einem gleichwertigen Framework. So jetzt aber ein Beispiel. Die nachfolgende Klasse ist nur ein Dummy Objekt und soll verschiedene Einsatzmöglichkeiten zeigen.
Da ich sehr viel mit Interfaces arbeite, wollte ich das jede Implementation einer Klasse von einem Interface abstammt. So sollte INathanDummy ein Property auf IAnotherDummy haben und nicht auf TAnotherDummy.
Meiner Meinung nach, hat Embarcadero ein voll nutzbares JSON Framework herausgebracht, hoffentlich kommen noch ein paar brauchbare TConverter mit den nächsten Versionen hinzu.
Vielen Dank...
Es gibt 3 Namespaces, welche relevant sind. Zum einen "System.JSON.Serializers", für den eigentlichen Serialisierer und die Klassenattribute, zum anderen "System.JSON.Converters", für eigene Konverter und zuletzt "System.JSON.Types", für z.B. Datumsformate oder Formatierungen.
TJsonSerializer
Ist die wichtigste Klasse, sie ist implementiert im Namespace System.JSON.Serializers.Diese Klasse ist dafür zuständig, ein Objekt in JSON (Serialisierung) zu formatieren und ein Objekt durch Lesen bzw. Interpretieren einer JSON-Struktur (Deserialisierung) zu erstellen. Hier mal mein Wrapper:
unit Nathan.Lib.Marshaller.Json;
interface
uses
System.JSON.Serializers;
type
TNathanSerializer = class
private
FSerializer: TJsonSerializer;
public
constructor Create();
destructor Destroy(); override;
function Deserialize<T>(const data: string): T;
function Serialize<T>(const obj: T): string;
end;
implementation
uses
System.JSON.Types;
{ TNathanSerializer }
constructor TNathanSerializer.Create();
begin
inherited Create;
FSerializer := TJsonSerializer.Create;
// FSerializer.Formatting := TJsonFormatting.Indented; // Line Breaks...
FSerializer.DateFormatHandling := TJsonDateFormatHandling.Iso;
end;
destructor TNathanSerializer.Destroy();
begin
FSerializer.Free;
inherited;
end;
function TNathanSerializer.Deserialize<T>(const data: string): T;
begin
Result := FSerializer.Deserialize<T>(data);
end;
function TNathanSerializer.Serialize<T>(const obj: T): string;
begin
Result := FSerializer.Serialize<T>(obj);
end;
end.
Mein Wrapper dient dazu, andere JSON Serializer zu Testen. Letztendlich bin ich beim Original geblieben. Ich kann mit meinem Wrapper so vorkonfigurieren, dass das Datumsformat usw. immer gleich ist.Jetzt aber das Wichtigste, wie nutze ich das ganze um nicht immer alles neu schreiben zu müssen. Bisher hatte ich immer das Problem, dass die Konvertierung in bzw. von JSON immer wieder dem gleichen Muster folgte. In anderen Sprachen war dies schon lange mit Klassenattributen durchzuführen. Nur in Delphi suchte ich nach einem gleichwertigen Framework. So jetzt aber ein Beispiel. Die nachfolgende Klasse ist nur ein Dummy Objekt und soll verschiedene Einsatzmöglichkeiten zeigen.
unit Nathan.Lib.Marshaller.DummyClass;
interface
uses
System.JSON.Serializers,
System.JSON.Converters,
System.Generics.Collections,
System.JSON.Writers,
System.TypInfo,
System.Rtti;
{$M+}
type
TFooType = (ftNormal, ftRunner, ftFast, ftFaster, ftFastest);
IAnotherDummy = interface
['{B4FFE2A1-EFAB-4243-9966-8C7DA2E4E29B}']
function GetAssembly(): string;
procedure SetAssembly(const Value: string);
property Assembly: string read GetAssembly write SetAssembly;
end;
INathanDummy = interface
['{261C4E59-3DF2-4756-9892-2D7E8C67E1E4}']
function GetId(): Integer;
function GetMessageBody(): string;
function GetInternalType(): TFooType;
function GetVersion(): Integer;
function GetAnother(): IAnotherDummy;
procedure SetId(Value: Integer);
procedure SetMessageBody(const Value: string);
procedure SetInternalType(Value: TFooType);
procedure SetVersion(Value: Integer);
procedure SetAnother(Value: IAnotherDummy);
property Id: Integer read GetId write SetId;
property MessageBody: string read GetMessageBody write SetMessageBody;
property InternalType: TFooType read GetInternalType write SetInternalType;
property Version: Integer read GetVersion write SetVersion;
property Another: IAnotherDummy read GetAnother write SetAnother;
end;
[JsonSerialize(TJsonMemberSerialization.&In)]
TAnotherDummy = class(TInterfacedObject, IAnotherDummy)
strict private
[JsonIn]
[JsonName('Assembly')]
FAssembly: string;
private
function GetAssembly(): string;
procedure SetAssembly(const Value: string);
public
constructor Create();
property Assembly: string read GetAssembly write SetAssembly;
end;
TJsonCustomCreationConverterAnotherDummy = class(TJsonCustomCreationConverter<TAnotherDummy>)
protected
function CreateInstance(ATypeInf: PTypeInfo): TValue; override;
public
procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); override;
end;
/// <summary>
/// Demo Class to serialization in json.
/// Another class option for the new de-/serializer:
/// [JsonSerialize(TJsonMemberSerialization.&Public)] Serialize all public fields and properties from my and inherited classes...
/// [JsonSerialize(TJsonMemberSerialization.Fields)]
/// [JsonSerialize(TJsonMemberSerialization.&In)] Just only fields with attribute JsonIn...
/// </summary>
[JsonSerialize(TJsonMemberSerialization.&In)]
TNathanDummy = class(TInterfacedObject, INathanDummy)
strict private
[JsonIgnore] // Ignore this field when serializing...
FId: Integer;
[JsonIn]
[JsonName('messagebody')]
FMessageBody: string;
[JsonIn]
[JsonName('type')]
[JsonConverter(TJsonEnumNameConverter)]
FInternalType: TFooType;
[JsonIn]
[JsonName('ver')]
FVersion: Integer;
[JsonIn]
[JsonName('Another')]
[JsonConverter(TJsonCustomCreationConverterAnotherDummy)]
FAnother: IAnotherDummy;
private
function GetId(): Integer;
function GetMessageBody(): string;
function GetInternalType(): TFooType;
function GetVersion(): Integer;
function GetAnother(): IAnotherDummy;
procedure SetId(Value: Integer);
procedure SetMessageBody(const Value: string);
procedure SetInternalType(Value: TFooType);
procedure SetVersion(Value: Integer);
procedure SetAnother(Value: IAnotherDummy);
public
constructor Create(AId: Integer; const AMessageBody: string; AType: TFooType; AVersion: Integer); overload;
destructor Destroy(); override;
property Id: Integer read GetId write SetId;
property MessageBody: string read GetMessageBody write SetMessageBody;
property InternalType: TFooType read GetInternalType write SetInternalType;
property Version: Integer read GetVersion write SetVersion;
property Another: IAnotherDummy read GetAnother write SetAnother;
end;
{$M-}
implementation
{ TAnotherDummy }
constructor TAnotherDummy.Create;
begin
inherited;
FAssembly := 'Baugruppe';
end;
function TAnotherDummy.GetAssembly: string;
begin
Result := FAssembly;
end;
procedure TAnotherDummy.SetAssembly(const Value: string);
begin
FAssembly := Value;
end;
{ TJsonCustomCreationConverterAnotherDummy }
function TJsonCustomCreationConverterAnotherDummy.CreateInstance(ATypeInf: PTypeInfo): TValue;
begin
Result := TAnotherDummy.Create;
end;
procedure TJsonCustomCreationConverterAnotherDummy.WriteJson(
const AWriter: TJsonWriter; const AValue: TValue;
const ASerializer: TJsonSerializer);
var
AnotherDummy: IAnotherDummy;
begin
if AValue.TryAsType(AnotherDummy) then
ASerializer.Serialize(AWriter, AnotherDummy as TAnotherDummy);
end;
{ TNathanDummy }
constructor TNathanDummy.Create(AId: Integer; const AMessageBody: string; AType: TFooType; AVersion: Integer);
begin
inherited Create;
FId := AId;
FMessageBody := AMessageBody;
FInternalType := AType;
FVersion := AVersion;
FAnother := TAnotherDummy.Create;
end;
destructor TNathanDummy.Destroy();
begin
FAnother := nil;
inherited;
end;
function TNathanDummy.GetAnother: IAnotherDummy;
begin
Result := FAnother;
end;
function TNathanDummy.GetId: Integer;
begin
Result := FId;
end;
function TNathanDummy.GetInternalType: TFooType;
begin
Result := FInternalType;
end;
function TNathanDummy.GetMessageBody: string;
begin
Result := FMessageBody;
end;
function TNathanDummy.GetVersion: Integer;
begin
Result := FVersion;
end;
procedure TNathanDummy.SetAnother(Value: IAnotherDummy);
begin
FAnother := Value;
end;
procedure TNathanDummy.SetId(Value: Integer);
begin
FId := Value;
end;
procedure TNathanDummy.SetInternalType(Value: TFooType);
begin
FInternalType := Value;
end;
procedure TNathanDummy.SetMessageBody(const Value: string);
begin
FMessageBody := Value;
end;
procedure TNathanDummy.SetVersion(Value: Integer);
begin
FVersion := Value;
end;
end.
Da ich sehr viel mit Interfaces arbeite, wollte ich das jede Implementation einer Klasse von einem Interface abstammt. So sollte INathanDummy ein Property auf IAnotherDummy haben und nicht auf TAnotherDummy.
[JsonSerialize(TJsonMemberSerialization.&In)]
Das Klassenattribut bestimmt, dass nur Felder mit dem Attribut [JsonIn] berücksichtigt werden. Analog könnte man auch das Attribut [JsonIgnore] verwenden, welches dann bedeutet, dass das Property nicht vom Marshaller berücksichtigt wird.[JsonName('messagebody')]
Bestimmt den Bezeichner im JSON. Fehlt er, heißt der Bezeichner wie das Feld. Ist zwar Ok, sollte nur zwischen den eigenen Programmen JSON Daten ausgetauscht werden, aber wann kommt dies vor. Frontend ist schnell und einfach in Delphi geschrieben. Backend kann aber mit einer ganz anderen Sprache implementiert sein.[JsonConverter(TJsonEnumNameConverter)]
Konvertiert mir mein Enum TFooType in den Plaintext Namen. Sonst steht im JSON nur so was wie {"type":1} anstelle von {"type":"ftRunner"}, ist doch viel leserlicher und jeder Programmierer weiss sofort was gemeint ist.[JsonConverter(TJsonCustomCreationConverterAnotherDummy)]
Bevor ich den TJsonCustomCreationConverter<T> verwendete, war das Property "FAnother" immer leer im JSON, obwohl ich es im Konstruktor initialisierte. Um eine Klassenproperty zu serialisieren, brauchte ich einen eigenen "JSON Custom Creation Converter". Das Attribut bestimmt, welcher Converter für das Klassenproperty zu verwenden ist. In meinem Fall brauchte ich eine Implementation von CreateInstance() um das Unmarshalling also JSON to Object durchzuführen und eine Implementation von WriteJson() um das Marshalling durchzuführen.Conclusion
Hoffe ich konnte damit Helfen die Dokumentation mit Beispielen zu ergänzen. Test, voller Sourcecode usw. findet man auf Github unter: https://github.com/Thurnreiter/GeneralStuff/tree/master/Thurnreiter.LibMeiner Meinung nach, hat Embarcadero ein voll nutzbares JSON Framework herausgebracht, hoffentlich kommen noch ein paar brauchbare TConverter mit den nächsten Versionen hinzu.
Vielen Dank...
Kommentare
Kommentar veröffentlichen