Generics and JSON serialisation in Delphi XE5 reflection - json

I'm trying to serialise/deserialise the TMySerializableClass declared in the below unit:
unit Unit6;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Generics.Collections, System.SyncObjs, DBXJSon,
DBXJSonReflect;
type
TForm6 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
public
{ Public declarations }
end;
TMySerializableClass<T> = class
MyStringField: string;
MyIntegerField: Integer;
MyBooleanField: Boolean;
end;
var
Form6: TForm6;
implementation
{$R *.dfm}
procedure TForm6.Button1Click(Sender: TObject);
var
Mar: TJSONMarshal; //Serializer
UnMar: TJSONUnMarshal; //UnSerializer
SerializedObject: TJSONObject;
aMySerializableClass1: TMySerializableClass<Integer>;
aMySerializableClass2: TMySerializableClass<Integer>;
aString: string;
begin
try
aMySerializableClass1:= TMySerializableClass<Integer>.Create;
aMySerializableClass2:= TMySerializableClass<Integer>.Create;
Mar:= TJSONMarshal.Create(TJSONConverter.Create);
try
SerializedObject := Mar.Marshal(aMySerializableClass1) as TJSONObject;
finally
Mar.Free;
end;
Memo1.Text:= SerializedObject.ToString;
// UnMarshalling Kid
UnMar := TJSONUnMarshal.Create;
try
aMySerializableClass2 := UnMar.UnMarshal(SerializedObject) as TMySerializableClass<Integer>;
finally
UnMar.Free;
end;
finally
aMySerializableClass1.Free;
aMySerializableClass2.Free;
end;
end;
end.
When I serialise it everything works fine, while when deserialise to a new instantiated variable of the same kind I got the following error:
First chance exception at $74E5C41F. Exception class EConversionError with message 'Internal: Cannot instantiate type Unit6.TMySerializableClass<System.Integer>'. Process Project7.exe (2252)
Edit: this has something to do with the TMySerializableClass<T>, which
is a generic. If I declare it as TMySerializableClass the
deserialisation works fine.
Ideas?

Cannot test on XE5 but on Berlin 10.1 it worked for me after I added alias for instance of generic type as
TMySerializableClassInteger = TMySerializableClass<Integer>;
looks like Delphi does not generate RTTI information if you don't do it.
UPDATE. Seems Delphi does not need alias but just have some declaration of specialized class in interface part of the unit. So if you declare variable or form field as TMySerializableClass<Integer> it will also work.

Related

how do I use setstate (lnet)?

When I use setstate I always get this error: unit1.pas(34,49) Error: Only class methods, class properties and class variables can be referred with class references, why does this error occur? I think the error lays in ssNoDelay, as without it the before mentioned error doesn't happen.
I made this empty project as an example:
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, lNetComponents, lNet;
type
{ TForm1 }
TForm1 = class(TForm)
LTCPComponent1: TLTCPComponent;
procedure FormCreate(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
LTCPComponent1.SocketClass.SetState(ssNoDelay);
end;
end.
SetState has to be used on a TLSocket, not on the TLCTPComponent. This means you can only use SetState when you already have a connection you want to apply one of the options (ssNoDelay for example) on (so the best solution is to use setstate in the OnAccept / OnConnect procedure if you want to use an option from the start).

EConvert exception invalid date time when trying to convert json to object using REST.Json

The following code end up in an exception
'2019.10.5 14:16:14,1000' is not a valid date and time
when trying to parse the json to an object. The problem seems to be the decimal in the date.
JSonStr := '{"orderNumber": "395409772020_1", "modified": "2019-10-05T14:16:14.9995946Z"}';
Order := TJson.JsonToObject<TOrder>(JSonStr);
If I use a date with millisecond precision that rounds downwards i.e "modified": "2019-10-05T14:16:14.4995946Z" it works fine.
I've tried adding options to set the format for the date. Order := TJson.JsonToObject<TOrder>(JSonStr, [joDateFormatParse]);. This prevents the code from crashing, but the DateTime is not recognized and the value ends up with "0".
Anyway around this, or is it simply a bug in the library?
I'm running Delphi 10.2 Update 3
I built a simple demo program with your code and it works perfectly with Delphi 10.4.1.
Here is the source code for the demo:
unit JsonParseDateDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
REST.Json;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
end;
type
TOrder = class
private
FOrderNumber : String;
FModified : TDateTime;
published
property OrderNumber : String read FOrderNumber write FOrderNumber;
property Modified : TDateTime read FModified write FModified;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
JsonStr : String;
Order : TOrder;
begin
JsonStr := '{"orderNumber": "395409772020_1", ' +
'"modified": "2019-10-05T14:16:14.9995946Z"}';
Order := TJson.JsonToObject<TOrder>(JsonStr);
Memo1.Lines.Add(Order.OrderNumber);
Memo1.Lines.Add(DateTimeToStr(Order.Modified));
Order.Free;
end;
end.
It is a bug in Delphi version you use.

Delphi to change JSONMarshalledAttribute in runtime

I have a class in Delphi which I export in jsonmarshalled file.
I am skipping some Fields using the JSONMarshalledAttribute, which resides in the unit: REST.JSON.Types. More literature here
[JSONMarshalledAttribute(False)]
Field1: double;
[JSONMarshalledAttribute(False)]
Field2: double;
So far this works great.
My question is: Can I change the JSONMarshalledAttribute to True during runtime?
EDIT 1:
As requested here is the code:
Suppose we have a Form2:TForm and within the form as follows...:
Interface(I am skipping the attributes of the form....)
type
TmyClass = class(Tobject)
private
[JSONMarshalledAttribute(false)]
FName: string;
FVal1: double;
public
property Name: string read FName write FName;
property Val1: double read FVal1 write FVal1;
end;
and then in the implementation:
procedure TForm2.Button2Click(Sender: Tobject);
var
LArray: TJSONArray;
begin
MyClass := TmyClass.Create;
MyClass.name := 'myNAme';
LArray := myMarshaler(MyClass, 'FName', True);
end;
and the actual function that returns a TJSONArray:
function TForm2.myMarshaler(myclass: TmyClass; Field: string; Marshal: Boolean)
: TJSONArray;
var
Marshaler: TJSONMarshal;
JSONObject: TJSONObject;
LArray: TJSONArray;
begin
Marshaler := TJSONMarshal.Create(TJSONConverter.Create);
try
Marshaler.RegisterJSONMarshalled(myclass, Field,Marshal);
// Marshaler.DateFormat := jdfUnix;
JSONObject := Marshaler.Marshal(myclass) as TJSONObject;
LArray := TJSONArray.Create;
LArray.AddElement(JSONObject);
result := LArray;
finally
FreeAndNil(Marshaler);
end;
end;
That will not work because Marshaler.RegisterJSONMarshalled requires a TClass as an argument type, but I want to input my own custom classes which are derived from TObject.
and this is the error:
[dcc32 Error] Unit2.pas(134): E2250 There is no overloaded version of >'RegisterJSONMarshalled' that can be called with these arguments
How do I fix this?
You can not change the attribute, but you can overwrite it.
According to the documentation, it should work with:
Marshaler.RegisterJSONMarshalled(TYourClass, 'Field1', true);
Therefore, you can not use the class function TJson.ObjectToJsonObject(...), - you'll have to create the marshaller (from the unit REST.JsonReflect) yourself. Example:
var
Marshaler: TJSONMarshal;
JSONObject: TJSOnObject;
begin
Marshaler := TJSONMarshal.Create(TJSONConverter.Create);
try
Marshaler.RegisterJSONMarshalled(TYourClass, 'Field1');
Marshaler.DateFormat :=jdfUnix;
JSONObject := Marshaler.Marshal(AObject) as TJSOnObject;
Result := JSONObject;
finally
FreeAndNil(Marshaler);
end;
end;
To remove the overwritten value you can call UnregisterJSONMarshalled.
Update to clarify how to this method is called:
The declared method signature is:
RegisterJSONMarshalled(clazz: TClass; Field: string; Marshal: Boolean);
So there are three parameters to pass in:
Marshaler.RegisterJSONMarshalled(myclass.ClassType, Field, Marshal);
or even more simpler:
Marshaler.RegisterJSONMarshalled(TMyClass, Field, Marshal);
You have to pass in the class type of your class.
No you cannot change attributes at runtime. You'll have to find a different approach to handle the dynamic nature of your marshaling.

Delphi: Undeclared Identifier in Procedure

I want to edit a property of a shape inside a procedure.
However if I create my own procedure I get an "undefinded identifier" error.
I tried to edit the property in the OnCreate event procedure of my form and that works just fine.
Why is it like that and how can I fix it?
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;
type
Tfrm_main = class(TForm)
shp_wheelLeftInside: TShape;
shp_wheelRightInside: TShape;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frm_main: Tfrm_main;
implementation
{$R *.dfm}
procedure addWheelInsides();
begin
shp_wheelRightInside.Height := 42; //this is where the error occurs
end;
procedure Tfrm_main.FormCreate(Sender: TObject);
begin
shp_wheelLeftInside.Height := 42;
shp_wheelRightInside.Height := 42;
addWheelInsides();
end;
end.
The problem is that shp_wheelRightInside is a field that belongs to your Tfrm_main class whereas the addWheelInsides() method you have declared as a naked, ordinary method that belongs to nothing. The method, therefore, does not have access to the fields which belong to the form.
One solution is to move the method, which intends to operate on objects owned by the form, into the form itself.
Tfrm_main = class(TForm)
shp_wheelLeftInside: TShape;
shp_wheelRightInside: TShape;
procedure FormCreate(Sender: TObject);
private
procedure addWheelInsides(); {declare it here}
public
{ Public declarations }
end;
Which you then implement as a method of the form class as :
procedure Tfrm_main.addWheelInsides();
begin
shp_wheelRightInside.Height := 42;
end;
The shp_wheelRightInside field is not visible in your procedure.
Declare the procedure addWheelInsides() inside the form as a method instead to resolve the shp_wheelRightInside scope.
type
Tfrm_main = class(TForm)
shp_wheelLeftInside: TShape;
shp_wheelRightInside: TShape;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure addWheelInsides;
public
{ Public declarations }
end;
If you want to extend the procedure across several units, pass TShape as a parameter instead.

idhttp.get component doesnt download full HTML File

I am currently working on a program that should download the HTML file of an Wikipedia Page and then convert it from HTML to PDF.
My program so far downloads the HTML file, but without any pictures and the page looks incomplete.
Is there any way I can bring the idhttp.get component from the INDY Package to download the full HTML file?
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
IdHTTP;
type
TForm3 = class(TForm)
Label1: TLabel;
Button1: TButton;
Button2: TButton;
IdHTTP1: TIdHTTP;
SpeicherEd: TEdit;
SaveDialog1: TSaveDialog;
AuswahlB: TButton;
NameEd: TEdit;
procedure Button1Click(Sender: TObject);
procedure NameEdEnter(Sender: TObject);
procedure AuswahlBClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Speicherort, Form3: TForm3;
implementation
uses
unit2, unit1, Filectrl;
{$R *.dfm}
procedure TForm3.Button1Click(Sender: TObject);
var ResponseStream: TFileStream;
begin
ResponseStream := TFileStream.Create(SpeicherEd.Text, fmCreate);
try
idHTTP1.Get(url, ResponseStream);
finally
ResponseStream.Free;
end;
ShowMessage('Download abgeschlossen');
end;
procedure TForm3.AuswahlBClick(Sender: TObject);
var dir : String;
begin
dir := ExtractFilePath(Application.ExeName);
if SelectDirectory ('Bitte ein Verzeichnis auswählen','',Dir)
then
SpeicherEd.Text := (dir+'\'+NameEd.text+'.html');
end;
procedure TForm3.NameEdEnter(Sender: TObject);
begin
NameEd.Text := '';
end;
end.