Einzelnen Beitrag anzeigen

Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#9

AW: Binärdaten Platzsparent in JSON speichern

  Alt 25. Jul 2015, 19:06
Wenn es um JSON geht, dann geht es auch immer um Austausch mit einem anderen System. Und da würde ich immer etwas wählen, was sich auf beiden Systemen leicht umsetzen lässt.

BASE64 ist ein Standard und BASE85 ist eher ein Exot.

Und nochmal zum Verständnis:

Man kann nicht in einen AnsiString etwas hineinkomprimieren. Wer so etwas versucht/macht, macht auf jeden Fall etwas falsch und es ist eher ein Zufall, wenn das zuverlässig funktioniert.

Auch einen String kann ich nicht ohne weiters komprimieren, dieser benötigt zunächst die Umwandlung in eine Bytefolge und diese wird komprimiert. Und abhängig vom Encodig kommen da auch unterschiedliche Bytefolgen heraus. Ein Blick in die Quellen von ZLib zeigt dir was ich meine.

Im Übrigen habe ich in der ZLib (XE8) einen Bug entdeckt. So wird dort der CompressionLevel nicht berücksichtigt, wenn man die Routine benutzt, die direkt einen String komprimiert.
(Der String wird dort mit TEncoding.UTF16.GetBytes(); in eine Bytefolge umgewandelt).

Die Bytefolge, die ich von dort wieder erhalten, kann man sich dann mit TNetEncoding.Base64 wieder in eine String-Repräsentation überführen.

Hier mal ein Beispiel, wie man dies umsetzt und im Anhang ein komplettes Test-Projekt, denn gerade die Schnittstellen nach draussen sollten immer getestet werden.
Delphi-Quellcode:
unit Outside.Impl.FooService;

interface

uses
  Inside.Foo,
  Outside.IFooWebService,
  Outside.IFooService,
  System.Threading,
  System.ZLib;

type
  TFooDTO = class
  private
    FData: string;
  public
    /// <summary>
    /// Compressed UTF16-String as BASE64
    /// </summary>
    property Data: string read FData write FData;
  end;

  TFooAssembler = class
  private
    FCompressionLevel: TZCompressionLevel;
  public
    property CompressionLevel: TZCompressionLevel read FCompressionLevel write FCompressionLevel;
    function Convert( AFoo: TFooDTO ): TFoo; overload;
    function Convert( AFoo: TFoo ): TFooDTO; overload;
  end;

  TFooService = class( TInterfacedObject, IFooService )
  private
    FFooWebService: IFooWebService;
    FFooAssembler: TFooAssembler;
    procedure PostFoo( AFoo: TFoo ); overload;
    function PostFoo( AFoo: TFoo; callback: TResultAction ): ITask; overload;
    procedure GetFoo( out AFoo: TFoo ); overload;
    function GetFoo( callback: TObjectResultAction<TFoo> ): ITask; overload;
  public
    constructor Create( AFooWebService: IFooWebService );
    destructor Destroy; override;
  end;

implementation

uses
  REST.Json, REST.JsonReflect,
  System.NetEncoding,
  System.SysUtils;

{ TFooAssembler }

function TFooAssembler.Convert( AFoo: TFoo ): TFooDTO;
var
  LFooDTO: TFooDTO;
  LInBuffer, LOutBuffer: TBytes;
begin
  LFooDTO := TFooDTO.Create;
  try
    // String mit der entsprechenden Kodierung in eine Byte-Folge umwandeln
    LInBuffer := TEncoding.Unicode.GetBytes( AFoo.Data );

    // Die Byte-Folge komprimieren
    ZCompress(
      {inBuffer} LInBuffer,
      {outBuffer} LOutBuffer,
      {level} CompressionLevel );

    // Die komprimierte Byte-Folge in einen BASE64-String kodieren
    LFooDTO.Data := TNetEncoding.Base64.EncodeBytesToString( LOutBuffer );

    Result := LFooDTO;
    LFooDTO := nil;
  finally
    LFooDTO.Free;
  end;
end;

function TFooAssembler.Convert( AFoo: TFooDTO ): TFoo;
var
  LFoo: TFoo;
  LInBuffer, LOutBuffer: TBytes;
begin
  LFoo := TFoo.Create;
  try
    // BASE64 String decodieren in eine Byte-Folge
    LInBuffer := TNetEncoding.Base64.DecodeStringToBytes( AFoo.Data );

    // Byte-Folge entkomprimieren
    ZDecompress(
      {inBuffer} LInBuffer,
      {outBuffer} LOutBuffer );

    // UTF16-String aus der entkomprimierten Byte-Folge lesen
    LFoo.Data := TEncoding.Unicode.GetString( LOutBuffer );

    Result := LFoo;
    LFoo := nil;
  finally
    LFoo.Free;
  end;
end;

{ TFooService }

constructor TFooService.Create( AFooWebService: IFooWebService );
begin
  inherited Create;
  FFooWebService := AFooWebService;
  FFooAssembler := TFooAssembler.Create;
  FFooAssembler.CompressionLevel := TZCompressionLevel.zcMax;
end;

destructor TFooService.Destroy;
begin
  FFooAssembler.Free;
  inherited;
end;

function TFooService.GetFoo( callback: TObjectResultAction<TFoo> ): ITask;
begin
  Result := TTask.Run(
    procedure
    var
      LFoo: TFoo;
      LDispose: Boolean;
    begin
      LDispose := True;
      try
        try
          GetFoo( LFoo );
          callback( LFoo, nil, LDispose );
        except
          on E: Exception do
            callback( nil, E, LDispose );
        end;
      finally
        if LDispose then
          LFoo.Free;
      end;
    end );
end;

procedure TFooService.GetFoo( out AFoo: TFoo );
var
  LFooDTO: TFooDTO;
  LFooStr: string;
begin
  FFooWebService.GetFoo( LFooStr );

  LFooDTO := TJson.JsonToObject<TFooDTO>( LFooStr );
  try
    AFoo := FFooAssembler.Convert( LFooDTO );
  finally
    LFooDTO.Free;
  end;
end;

procedure TFooService.PostFoo( AFoo: TFoo );
var
  LFooDTO: TFooDTO;
  LFooStr: string;
begin
  LFooDTO := FFooAssembler.Convert( AFoo );
  try
    LFooStr := TJson.ObjectToJsonString( LFooDTO );
    FFooWebService.PostFoo( LFooStr );
  finally
    LFooDTO.Free;
  end;
end;

function TFooService.PostFoo( AFoo: TFoo; callback: TResultAction ): ITask;
begin
  Result := TTask.Run(
    procedure
    begin
      try
        PostFoo( AFoo );
        callback( nil );
      except
        on E: Exception do
          callback( E );
      end;
    end );
end;

end.
Hinweis: Das Projekt im Anhang ist ein DUnitX-Testprojekt und geschrieben mit Delphi XE8!
Angehängte Dateien
Dateityp: zip dp_185991.zip (5,9 KB, 13x aufgerufen)
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)

Geändert von Sir Rufo (25. Jul 2015 um 19:25 Uhr)
  Mit Zitat antworten Zitat