![]() |
TJSONMarshal / TJSONUnMarshal böse Falle
Ich frage mich seit geraumen warum eine App plötzlich Timestamps falsch berechnet.
Der Grund war das Encoding / Decoding mit TJSONMarshal / TJSONUnMarshal. Das hat bis dato gut funktioniert, Object zu String, und zurück, aber seit womöglich 10.1 Berlin gab es da plötzlich Probleme. Normalerweise debugge ich nicht tief in die System Sourcen, aber hier musste es mal wieder sein :( Der Grund war das TJSONMarshal / TJSONUnMarshal anscheinend neuerdings unterschiedliche IsDateTimeUTC settings per Default eingestellt haben. Kann jetzt nicht genau sagen seit wann ... Die Lösung war das explizit mit anzugeben, um die Konvertierungen kompatibel zu machen, also
Delphi-Quellcode:
für Marshal/Unmarshal:
LMar.DateTimeIsUTC := True;
Delphi-Quellcode:
function TParcel.InternalMarshal_ToString : String;
var LMar : TJSONMarshal; begin LMar := TJSONMarshal.Create(); // This doesn't correctly set DateTimeUTC by default try LMar.DateTimeIsUTC := True; // !!! Important to ensure same Coding/Decoding Result := LMar.Marshal(Self).ToString; // as TJSonObject; finally LMar.Free; end; end; function TParcel.InternalUnmarshal_FromObject(value: TJSONObject): TParcel; var LMar: TJSONUnMarshal; begin Result := nil; if not Assigned( value ) then Exit; LMar := TJSONUnMarshal.Create(); // This sets DateTimeIsUTC internally by default try LMar.DateTimeIsUTC := True; // !!! Important to ensure same Coding/Decoding ... ... ... finally LMar.Free; end; end; Wenn es nicht gleich gesetzt wird kann es zu einer Zeitdifferenz dei Encode/Decode kommen von 2:00h. Das ist definitiv nicht das was ich erwarten würde :stupid: Rollo |
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Traue niemals der Delphi-RTL, schreibe Unit-Tests! :warn:
Zumindest in 10 Seattle ergibt ein
Delphi-Quellcode:
im JSON noch ein
EncodeDateTime(1988, 10, 21, 17, 45, 39, 999)
Delphi-Quellcode:
.
'1988-10-21T17:45:39.999Z'
|
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Kannst du dafür noch einen Eintrag in QP erstellen?
|
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Ich verstehe das Problem noch nicht. Ein TDateTime ist lokale Zeit, nicht UTC. Von TObject nach JSON nehmen wir kein UTC an, von Json nach TObject schon. Ich denke, das ist doch so richtig, oder?
Delphi-Quellcode:
program UTCMarshalTest;
{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.DateUtils, System.Types, System.Json, REST.JsonReflect, Unit1 in 'Unit1.pas'; procedure p(); var marshal: TJsonMarshal; unmarshal: TJSONUnMarshal; myObject: TMyObject; timestamp: TDateTime; myObject2: TMyObject; begin marshal := nil; unmarshal := nil; myObject := nil; myObject2 := nil; try marshal := TJSONMarshal.Create(); unmarshal := TJSONUnMarshal.Create(); myObject := TMyObject.Create(); timestamp := EncodeDateTime(2017, 10, 02, 10, 28, 44, 123); myObject.FTimeStamp := timestamp; myObject2 := unmarshal.CreateObject( TMyObject, marshal.Marshal(myObject) as TJsonObject ) as TMyObject; Assert( CompareDateTime(myObject.FTimeStamp, myObject2.FTimeStamp) = System.Types.EqualsValue ); finally marshal.Free(); unmarshal.Free(); myObject.Free(); myObject2.Free(); end; end; begin try p(); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; WriteLn(sLineBreak, 'end.'); ReadLn; end. |
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Das Problem ist, daß dein Code unter Tokyo eine EAssertionFailed-Exception auslöst.
|
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Oh, ok. Unter Seattle läuft alles korrekt durch. Ich dachte er sei verwirrt dass
Delphi-Quellcode:
und
marshal
Delphi-Quellcode:
nicht die gleiche Datums/UTC-Eigenschaften hatten.
unmarshal
|
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Hallo Uwe,
Zitat:
Man muss es halt "vollständig" konfigurieren. Rollo |
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Hallo Günther,
Zitat:
dann dieses JSON wieder zu einem Objekt mit Unmarshal dekodiere. Wenn da im Objekt ein TDateTime Feld drin ist wird mit Marshal eine UTC Korrektur gemacht und dann bei Unmarshal aber nicht (oder umgekehrt, habs jetzt gerade nicht nachgesehen). Standardmässig wird das ISOEncode benutzt, welches UTC berücksichtigt. Das Ergebnis-Objekt hat dann danach zum Ausgangs-Objekt je nach Zeitzone eine Differenz von z.B. +2:00. Es muss im Marshal/Umarshal DateTimeIsUTC = True/False; expliziert gesetzt werden das ich mit oder ohne Korrektur arbeiten möchte, jedenfalls das es bei Beiden gleich sein sollte. Rollo |
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Zitat:
Delphi-Quellcode:
zustande, der eine entsprechende Anpassung in
TISODateTimeInterceptor
Delphi-Quellcode:
erfordert. Insofern ist dein Anwendungsfall ein wichtiger Testcase.
TJSONMarshal.Create
Berlin:
Delphi-Quellcode:
Tokyo:
constructor TISODateTimeInterceptor.Create(ADateTimeIsUTC: Boolean);
begin ConverterType := ctString; ReverterType := rtString; FDateTimeIsUTC := true; end;
Delphi-Quellcode:
constructor TISODateTimeInterceptor.Create(ADateTimeIsUTC: Boolean);
begin ConverterType := ctString; ReverterType := rtString; FDateTimeIsUTC := ADateTimeIsUTC; end; |
AW: TJSONMarshal / TJSONUnMarshal böse Falle
|
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Ich bin mir nicht sicher, ob die Erklärung nicht irreführend ist.
Delphi-Quellcode:
war der ursprüngliche Code (siehe Berlin in meinem Post) und hat implizit dafür gesorgt, daß TJSONMarshal/TJSONUnMarshal mit derselben Einstellung arbeiteten und das korrekte Ergebnis brachten. Allerdings wurde die jeweilige Einstellung von DateTimeIsUTC gar nicht ausgewertet (der Create-Parameter wurde ja ignoriert).
FDateTimeIsUTC := true;
In Tokyo ist das geändert worden (siehe Tokyo in meinem Post) und es wird der übergebene Parameter verwendet, was auch vollkommen richtig ist. Das bedeutet aber, daß in TJSONMarshal/TJSONUnMarshal jeweils diese Werte auch passend gesetzt werden müssen (also beide True oder beide False). Das ist ja auch die Beobachtung, die du bereits im Eingangspost gemacht hast. Der Fehler ist also nicht das
Delphi-Quellcode:
im
FDateTimeIsUTC := ADateTimeIsUTC
Delphi-Quellcode:
(was ja nur das falsche Verhalten korrigiert), sondern die fehlende Initialisierung von
TISODateTimeInterceptor.Create
Delphi-Quellcode:
im
DateTimeIsUTC := true
Delphi-Quellcode:
, um mit TJSONUnMarshal synchron zu sein.
TJSONMarshal.Create
Vermutlich sollte diese Initialisierung auch nicht in
Delphi-Quellcode:
gemacht werden, sondern in
TJSONMarshal.Create
Delphi-Quellcode:
, wo das DateTimeIsUTC ja bereits deklariert wird. Gleichzeitig sollte das DateFormat auch dort initialisiert werden, und zwar ebenfalls synchron zu der Initialisierung in TJSONUnMarshal.
TTypeMarshaller<TSerial>.Create
Noch besser wäre es sogar, wenn man sowohl DateFormat als auch DateTimeIsUTC bereits in der Basisklasse TMarshalUnmarshalBase deklariert und initialisiert. Dann würde das Problem gar nicht auftauchen. Ich habe mir mal erlaubt, den Text entsprechend anzupassen und auch einen Verweis auf den QP-Eintrag zu ergänzen, der dem Fix zugrunde liegt. |
AW: TJSONMarshal / TJSONUnMarshal böse Falle
Ok, sorry.
Hatte ich genau andersrum gelesen Berlin <-> Tokyo. Sollte Emba aber so oder so klar sein. Rollo |
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:01 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 by Thomas Breitkreuz