Automatically serializing a TObject to JSON using mormot - delphi

I am trying to serialize an TObject to JSON using the mORMot framework. Unfortunately, the result is always null.
The class I am trying to serialize is:
type ApmTime = class(TObject)
private
function currentTime() : String;
published
property Current_time: String read currentTime;
public
constructor Create;
end;
constructor ApmTime.Create;
begin
inherited;
end;
function ApmTime.currentTime() : String;
begin
result := TimeToStr(Now);
end;
And the corresponding mORMot method is defined in SynCommons:
currentTime := ApmTime.Create;
Write(ObjectToJSON(currentTime, [woFullExpand]));
This always returns null. After having single-stepped in TTextWriter.WriteObject (located in unit SynCommons), the following piece of code seems to be where the resulting json is set to null:
if not(woFullExpand in Options) or
not(Value.InheritsFrom(TList)
{$ifndef LVCL} or Value.InheritsFrom(TCollection){$endif}) then
Value := nil;
if Value=nil then begin
AddShort('null');
exit;
I am expecting something along the line:
{
"Current_time" : "15:04"
}

Ran into this yesterday and worked out what's going on, so for the benefit of future people stumbling over this problem as well:
If you only add SynCommons.pas to your uses clause, then the default DefaultTextWriterJSONClass is set to TTextWriter which only supports serializing particular class types as you've seen, and doesn't support arbitrary classes/objects. See the following lines in SynCommons.pas where this default is set:
var
DefaultTextWriterJSONClass: TTextWriterClass = TTextWriter;
Now, in order to support serializing arbitrary objects to JSON, this global variable needs to be changed from the default TTextWriter to TJSONSerializer.
This class is defined in mORMot.pas, and in fact, if you add mORMot.pas to your uses clause, its initialization will override the above default and set TJSONSerializer as the new default for you.
This behaviour is in fact documented in SynCommons.pas if you read carefully enough, e.g. see the comments for "SetDEfaultJSONClass()" class method:
// you can use this method to override the default JSON serialization class
// - if only SynCommons.pas is used, it will be TTextWriter
// - but mORMot.pas initialization will call it to use the TJSONSerializer
// instead, which is able to serialize any class as JSON
class procedure SetDefaultJSONClass(aClass: TTextWriterClass);
So in short: To fix your issue, just add mORMot.pas to your uses clause in addition to SynCommons.pas which you should already have.

Try add a write to the published property.
property Current_time: String read currentTime write SetCurrentTime.
A readonly property is not serialized. Also ApmTime should be based on TPersistent
type
ApmTime = class(TPersistent)

Related

Delphi OpenTools API get component property

I'm implementing a package to convert and auto-generate components in the delphi IDE. I'm aware that GExperts has a similar function but I need to customize some specific properties.
Right now I'm stuck on accessing the TADOQuery.SQL property, which is an instance of TStrings:
var
aVal : TValue;
aSqlS : TStrings;
begin
[...]
if (mycomp.GetComponentType = 'TADOQuery') then
if mycomp.GetPropValueByName('SQL', aVal) then
begin
aSqlS := TStrings(aVal.AsClass);
if Assigned(aSqlS) then <----- problem is here
ShowMessage(aSqlS.Text); <----- problem is here
end;
end;
I'm not really sure whether using TValue from RTTI is the correct way to go.
Thanks
Assuming GetPropValueByName() is returning a valid TValue (you did not show that code), then using aVal.AsClass is wrong since the SQL property getter does not return a metaclass type. It returns an object pointer, so use aVal.AsObject instead, or even aVal.AsType<TStrings>.
Update If comp is actually IOTAComponent than TValue is definitely wrong to use at all. The output of IOTAComponent.GetPropValueByName() is an untyped var that receives the raw data of the property value, or an IOTAComponent for TPersistent-derived objects:
var
aVal: IOTAComponent;
aSqlS : TStrings;
begin
[...]
if (mycomp.GetComponentType = 'TADOQuery') then
if mycomp.PropValueByName('SQL', aVal) then
ShowMessage(TStrings(aVal.GetComponentHandle).Text);
end;
However, a better option would be to access the actual TADOQuery object instead:
if (mycomp.GetComponentType = 'TADOQuery') then
ShowMessage(TADOQuery(comp.GetComponentHandle).SQL.Text);

Convert Integer to Enum in property getter/setter

I currently have an option management which works with properties, that use all the same getter/setter method but with an index.
This works fine for Ints and Bools, but I'd like to get this working for enums, too.
TIntegerOptions = (kInt1, kInt2, kInt3);
class property Integer1: Integer index kInt1 read GetOptionsValueInt write SetOptionsValueInt;
class property Integer2: Integer index kInt2 read GetOptionsValueInt write SetOptionsValueInt;
class property Integer3: Integer index kInt3 read GetOptionsValueInt write SetOptionsWertInt;
FOptionsListInt: array[TIntegerOptions] of IniIntObject; // Objects contain Inifile read&save functionalities
...
class function GetOptionsValueInt(const aOption: TIntegerOptions) : Integer
begin
Result := Ord(FOptionsListInt[aOption].GetValue());
end;
class procedure SetOptionsValueInt(const aOption: TIntegerOptions; const aValue: Integer);
begin
FOptionslistInt[aOption].ChangeTo(aValue);
end;
This is working so far, and now my problem:
TEnumOptions = (kEnum1, kEnum2, kEnum3);
TEnum1 = (e1o1, e1o2, e1o3);
TEnum2 = (e2o1, e2o2, e2o3);
TEnum3 = (e3o1, e3o2, e3o3);
// these props fail because my functions return / excpect Integers, not the matching Enums
class property Enum1: TEnum1 index kEnum1 read GetOptionsValueInt write SetOptionsValueEnum;
class property Enum2: TEnum2 index kEnum2 read GetOptionsValueInt write SetOptionsValueEnum;
class property Enum3: TEnum3 index kEnum3 read GetOptionsValueInt write SetOptionsValueEnum;
FOptionsListEnum: array[TEnumOptions] of IniIntObject; // Objects contain Inifile read&save functionalities
I marked the problems in the code. Can I somehow cast the Integer values returned by the getters/setters to the matching enum for each property?
For those who read the old question:
I decided to just use the same getter/setter for the enums, cause in the end they get saved as Integers, too. If I need to cast inside the getters somehow, I'll add them again, but i hoped for a solution inside the property-declaration.
Regarding your update, you want to use code like this:
class property Enum1: TEnum1 index kEnum1 read GetOptionsValueInt;
That fails to compile because GetOptionsValueInt returns a value of type Integer. Since the property has type TEnum1, that is a simple type mismatch. In order to have a property of type TEnum1, the getter must be a function that returns a value of type TEnum1.
If each of these properties has a different type, then you cannot share getter and setter. Shared indexed getters and setters can only be used for properties that share a common type. Since it looks like none of these properties share a type, you will not be able to share getters and setters.
Original answer to original question
You don't show all the declarations, so it's hard for us to know why your code does not compile. We don't know what FOptionsListeEnum is, and we don't know what Wert() is.
However, here's a complete program that demonstrates that enumerated types can be used as indexes for indexed properties:
{$APPTYPE CONSOLE}
type
TMyEnum = (evOne, evTwo);
TMyClass = class
private
class function GetValue(const Option: TMyEnum): Integer; static;
public
class property ValueOne: Integer index evOne read GetValue;
class property ValueTwo: Integer index evTwo read GetValue;
end;
class function TMyClass.GetValue(const Option: TMyEnum): Integer;
begin
Result := ord(Option);
end;
begin
Writeln(TMyClass.ValueOne);
Writeln(TMyClass.ValueTwo);
end.
So, that makes it clear that there's no issue using enumerated types as indices. In which case, what is your problem. Let's look at the code you have:
class function TKmpOption.GetOptionsWertEnum(const aOption: TEnumOptionen): Integer;
begin
Result := Ord(FOptionsListeEnum[aOption].Wert());
end;
class procedure TKmpOption.SetOptionsWertEnum(const aOption: TEnumOptionen; const aWert: Integer);
begin
FOptionslisteEnum[aOption].Aendern(aOption(aWert));
end;
If Ord(FOptionsListeEnum[aOption].Wert()) does not compile with a type mismatch error, then it would seem that Wert() is not an ordinal value.
As for aOption(aWert) that makes no sense at all. That can never compile. Perhaps you meant TEnumOptionen(aWert) but that also makes no sense. Why would the value be of the same type used to index the possible options.
I hope that the code above shows that the issue is not related to enumerated types as indexers and is in fact of a rather more prosaic nature.

Use Rtti to set method field

I'm using Delphi XE to write a base class, which will allow descending classes to have dll methods mapped by applying an annotation. However I get a typecasting error, which is understandable.
In essence the base class should look like this:
TWrapperBase = class
public
FLibHandle: THandle;
procedure MapMethods;
end;
procedure TWrapperBase.MapMethods;
var
MyField: TRttiField;
MyAttribute: TCustomAttribute;
pMethod: pointer;
begin
FLibHandle := LoadLibrary(PWideChar(aMCLMCR_dll));
for MyField in TRttiContext.Create.GetType(ClassType).GetFields do
for MyAttribute in MyField.GetAttributes do
if MyAttribute.InheritsFrom(TMyMapperAttribute) then
begin
pMethod := GetProcAddress(FLibHandle, (MyAttribute as TMyMapperAttribute).TargetMethod);
if Assigned(pMethod) then
MyField.SetValue(Self, pMethod); // I get a Typecast error here
end;
And a descending class could look like this:
TDecendant = class(TWrapperBase)
private type
TSomeDLLMethod = procedure(aParam: TSomeType); cdecl;
private
[TMyMapperAttribute('MyDllMethodName')]
FSomeDLLMethod: TSomeDLLMethod;
public
property SomeDLLMethod: TSomeDLLMethod read FSomeDLLMethod;
end;
I could implement this differently, by hard coding the linking for each method in an overriden 'MapMethods'. This would however require each descendant to do so which I'd like to avoid.
I know that the TValue as used in this case will contain a pointer and not of the correct type (procedure(aParam: TSomeType); cdecl; in this case).
My question: Is there a way to pass the pointer from 'GetProcAdress' as the correct type, or to set the field directly (for example by using the field address 'PByte(Self)+MyField.Offset', which you can use to set the value of a record property)?
With the old Rtti, this could be done but only for published properties and without any type checking:
if IsPublishedProp(Self, 'SomeDLLMethod') then
SetMethodProp(Self, 'SomeDLLMethod', GetProcAddress(FLibHandle, 'MethodName');
There are two problems:
First your EInvalidCast is caused by TValue being very strict about type conversions. You are passing in a Pointer and want to set a field of type TSomeDLLMethod. You need to explicitly pass a TValue that has the correct type info.
if Assigned(pMethod) then
begin
TValue.Make(#pMethod, MyField.FieldType.Handle, value);
MyField.SetValue(Self, value);
end;
Now you will run into another EInvalidCast exception which is triggered because of a bug in XE inside the GetInlineSize method of the Rtti.pas which returns 0 for a tkProcedure kind of type. I don't know in what version this got fixed but it does not exist anymore in XE5.
For XE this can be fixed by using a unit I wrote some while ago (and which I just updated to fix this bug): RttiPatch.pas.
I also reported the original issue because Pointer is assignment compatible to a procedure type so TValue should also handle this: http://qc.embarcadero.com/wc/qcmain.aspx?d=124010
You could try something like:
Move(pMethod, PByte(Self) + Field.Offset, SizeOf(Pointer));
or
PPointer(PByte(Self) + Field.Offset)^ := pMethod;

Delphi Web Script: How to Expose a Class via RTTI which contains a Method returning another (exposed) Class

I have this Delphi class
type
TAnotherClass = class
end;
TMyClass = class
function Foo: TAnotherClass;
end;
function TMyClass.Foo: TAnotherClass;
begin
Result := TAnotherClass.Create;
end;
Now I'd like to expose this class via "dwsRTTIExposer.pas":
myUnit.ExposeRTTI(TypeInfo(TMyClass));
myUnit.ExposeRTTI(TypeInfo(TAnotherClass));
My Script looks like that:
var a: TMyClass = TMyClass.Create;
var b: TAnotherClass;
b := a.Foo;
Unfortunatelly Delphi Web Script doesn't recognize the return value from TMyClass.Foo as a valid Script Class. Is there a possibility to do that without falling back to manually expose each method with an OnEval-Eventhandler?
ExposeRTTI currently doesn't support parameters of class type.
This is because returning a direct Delphi class in the script can be problematic, as the life-cycle of Delphi objects is arbitrary and undetermined (the Delphi-side object can be destroyed at any time without notice f.i.).
You don't have to expose each method manually, you can use the RTTI exposer for every methods that involves basic types, and only have to deal manually with methods involving class types.
That'll then leave you with having to decide how you want the script-side objects to be exposed, and what their relationship to the Delphi-side object is, which is something the RTTI provides no clues about.
For instance with your original code, the OnEval code would just create a new script object that wraps the method Result for each call.
But the RTTI signature of Foo would still be exactly the same if its implementation was changed to something like
TMyClass = class
private
FFoo: TAnotherClass;
public
function Foo: TAnotherClass;
end;
function TMyClass.Foo: TAnotherClass;
begin
if FFoo=nil then
FFoo := TAnotherClass.Create;
Result := FFoo;
end;
However in that case, the OnEval would have to be completely different, as you would have to return the same script-side object on subsequent calls, and you would also need to hook the script-side object's destructor to properly handle the consequences on the private FFoo field.
Once Delphi has truly garbage-collected objects, the constraint could be relaxed, but currently the only thing that gets close is TInterfacedObject, which is unsafe, and you still have to deal with manual event handlers to handles things like circular references or classes that disable the reference counting (like the VCL components).

What does .Add mean when used in a constructor?

I come from a vb/c# background and I am having difficulty understanding the meaning of part of the following code, specifically the bit 'self.fColConsignments.Add'
TConsignment = class(TCollectionItem)
constructor Create(Collection : TCollection); override;
...
function TIFCSUMMsg.AddConsignment: TConsignment;
begin
result := TConsignment(self.fColConsignments.Add);
end;
if you background is C#, don't missinterpret that line:
result := TConsignment(self.fColConsignments.Add);
it's just a type cast and not a constructor call. In C# it would look like:
result = (TConsignment)self.fColConsignments.Add;
Presumably fcolConsignments is a collection owned by the TIFCSUMMsg instance (Self). Add adds a new item to the collection and returns the reference as the result. The result is then cast to a TConsignment to fit the result type of the AddConsignment method.
self.fColConsignments.Add probably adds a new item into fColConsignments, which must be a collection or similar, and returns it. But the declared return type may be more generic than the actual object returned, then a typecast is applied by using TConsignment(object).
The code in your example IS NOT A CONSTRUCTOR.
In C++/C#/Java/(put your C descendant language here), constructors are nameless methods. So:
class TFoo {
TFoo() { // do something }
}
....
{
TFoo myFoo;
myFoo = new TFoo()
.....
}
This a typical construction on such languages. This is NOT how Delphi works.
Constructors in Delphi have names. The convention is that they are called .Create and
they can be static or virtual ones (like any method).
The code above can be converted to:
TFoo = class
constructor Create();
end;
...
constructor TFoo.Create()
begin
// Do something;
end;
....
// Creating an object
var
myFoo: TFoo;
begin
myFoo := TFoo.Create();
...
end;
The code you exemplified were not an constructor but a
kind of typecast.
You can get more information about this (typecasts and constructors)
in the Delphi Language Guide (or Object Pascal Language Guide, depending
on the Delphi version you have available).

Resources