You are not logged in.
TRange = record
Min, Max: Integer;
end;
TOffense = record
Damage, AttackSpeed: TRange;
end;
TEnemy = class(TPersistent)
private
fEnabled: Boolean;
fName: string;
fOffense: TOffense;
published
property Enabled: Boolean read fEnabled write fEnabled;
property Name: string read fName write fName;
property Offense: TOffense read fOffense;
end
Hello.
Can anyone help me to serialize objects of above class?
ObjectToJson skips the Offense property...
I tried to
const
__TRange = 'Min,Max Integer';
__TOffense = Damage,AttackSpeed TRange';
TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TRange), __TRange);
TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TOffense), __TOffense);
Compilation fails saying that TRange and TOffense have no type info.
I'm using Delphi 7. Is there really no easy way to serialize such a simple object?
Last edited by salowabicz (2017-12-02 16:42:06)
Offline
This is a bug/limitation of Delphi itself.
The record fields have not RTTI!
Check https://synopse.info/files/html/Synopse … ml#TITL_51
So don't use a TPersistent for the main object, but just another record.
It will work as expected.
Offline
Thanks for response. I cannot use record as I'm storing the objects in TObjectList. I changed it to
TRange = record
Min, Max: Integer;
end;
TOffense = record
Damage, AttackSpeed: TRange;
end;
TEnemy = class(TPersistent)
private
fEnabled: Boolean;
fName: string;
fOffense: TOffense;
published
property Enabled: Boolean read fEnabled write fEnabled;
property Name: string read fName write fName;
property DamageMin: Integer read fOffense.Damage.Min;
property DamageMax: Integer read fOffense.Damage.Max;
property AttackSpeedMin: Integer read fOffense.AttackSpeed.Min;
property AttackSpeedMax: Integer read fOffense.AttackSpeed.Max;
end;
and it works.. but seems not elegant to me. What is the recommended way to serialize TObjectList of objects with record/custom type fields?
Last edited by salowabicz (2017-12-02 22:37:02)
Offline
See example of serializing record in "10.1.3.2.4. Text-based definition"
Offline
I have read that and already tried it, see my first post. It doesn't work, because records doesn't have type info (at least in Delphi 7) as ab said.
I need to serialize TObjectList of objects of TEnemy class, which have a record property. Is there any way to do this, other than exposing each record field as a property or upgrading to Delphi 2010?
Last edited by salowabicz (2017-12-03 10:06:43)
Offline
Even on Delphi 2010, I'm not sure it will work.
The missing RTTI for record published properties has been fixed even after this revision.
For a TSQLRecord, you can use TSQLRecordProperties.RegisterCustomRTTIRecordProperty.
But for a plain class, you need custom serialization for the class.
Consider defining TEnemy not as a class, but as an object.
It will be easily serialized, and you will have methods to work with.
As a workaround, you may define the published property as a variant (storing the information as a TDocVariant object), with a public TOffense record property, and getter/setter methods.
Offline
In D7, you could try to define an additional ref-counted field within the record to force rtti generation for your simple record types.
See http://codeverge.com/embarcadero.delphi … pe/1040908
Offline
As a workaround, you may define the published property as a variant (storing the information as a TDocVariant object), with a public TOffense record property, and getter/setter methods.
TRange = record
Min, Max: Integer;
end;
TOffense = record
Damage, AttackSpeed: TRange;
end;
TEnemy = class(TPersistent)
private
fEnabled: Boolean;
fName: string;
function GetOffense: Variant;
procedure SetOffense(Value: Variant);
public
_Offense: TOffense;
published
property Enabled: Boolean read fEnabled write fEnabled;
property Name: string read fName write fName;
property Offense: Variant read GetOffense write SetOffense;
end;
function TEnemy.GetOffense: Variant;
begin
Result := _Obj(['Damage',_Obj(['Min',_Offense.Damage.Min,'Max',_Offense.Damage.Min]),
'AttackSpeed',_Obj(['Min',_Offense.AttackSpeed.Min,'Max',_Offense.AttackSpeed.Max]]);
end;
procedure TEnemy.SetOffense(Value: Variant);
begin
with _Json(Value) do
begin
_Offense.Damage.Min := Damage.Min;
...
end;
end;
Did you mean something like this?
Last edited by salowabicz (2017-12-03 15:55:41)
Offline
Adding a ref-counted field in the record is not a good idea, since it will slow down everything.
And the root cause of the problem is that a record published property has no RTTI in the property field of the class, anyway. So adding a ref-counted field won't be enough.
The more I think about it, the more I suspect that one of the following workaround may be good enough:
- defining individual properties, from each field of the record (as you did);
- using a published property as a variant (storing the information as a TDocVariant object) or a RawJSON, with a public TOffense record property, and getter/setter methods;
- write a custom serializer/deserializer for the whole TEnemy class (more work).
Offline
Yes, for the variant version, it is something like that.
For RawJSON serialization, this may be a way of doing it:
TRange = record
Min, Max: Integer;
end;
TOffense = record
Damage, AttackSpeed: TRange;
end;
TEnemy = class(TSynPersistent)
private
fEnabled: Boolean;
fName: string;
function GetOffense: RawJSON;
procedure SetOffense(Value: RawJSON);
public
Off: TOffense;
published
property Enabled: Boolean read fEnabled write fEnabled;
property Name: string read fName write fName;
property Offense: RawJSON read GetOffense write SetOffense;
end;
function TEnemy.GetOffense: RawJSON;
begin
result := JSONEncode([
'damage','{','min',Off.Damage.Min,'max',Off.Damage.Max,'}',
'attackspeed','{','min',Off.AttackSpeed.Min,'max',Off.AttackSpeed.Max,'}']);
end;
procedure RangeFromJSON(out Range: TRange; JSON: PUTF8Char);
var V: TPUtf8CharDynArray;
begin
JSONDecode(JSON, ['min', 'max'], V);
if V=nil then
exit;
Range.Min := GetInteger(V[0]);
Range.Max := GetInteger(V[1]);
end;
procedure TEnemy.SetOffense(Value: RawJSON);
var V: TPUtf8CharDynArray;
begin
JSONDecode(Value,['damage','attackspeed'],V,true);
if V=nil then
exit;
RangeFromJSON(Off.Damage, V[0]);
RangeFromJSON(Off.AttackSpeed, V[1]);
end;
(note that I've just added proper RawJSON support for class JSON serialization)
But of course, the variant seems more easy to write (also slightly slower).
Offline
...
And the root cause of the problem is that a record published property has no RTTI in the property field of the class, anyway. So adding a ref-counted field won't be enough...
Many thanks for your knowledgeable comments !
However, for the "won't be enough" part, as shown in a trivial example which is compilable under D7 and a SO post, it seems that adding a ref-counted field will be enough to generate RTTI for the record, for other records that contain it, and for the class that somehow publishes the record. Could you comment about or a possible counter-example ?
Offline
On a side note, adding dummy string to TRange was one of the first things I have tried. ObjectToJson still wouldn't parse it. Curious about ab's answer though.
I have one last question, as I ran into a different issue. I thought about using a TStringList field. Is it supported out of the box?
TEnemy = class(TPersistent)
private
fEnabled: Boolean;
fName: string;
fList: TStringList;
public
constructor Create;
destructor Destroy; override;
published
property Enabled: Boolean read fEnabled write fEnabled;
property Name: string read fName write fName;
property List: TStringList read fList write fList;
end;
destructor TEnemy.Destroy;
begin
fList.Free;
end;
constructor TEnemy.Create;
begin
fList := TStringList.Create;
end;
When using it like this:
var
en: TEnemy;
begin
en := TEnemy.Create;
en.List.Add('test');
ol := TObjectList.Create;
ol.Add(en);
ObjectToJsonFile(ol, 'test.json');
ol.Clear;
JsonFileToObject('test.json', ol, TEnemy);
ShowMessage(IntToStr(ol.count));
ol.Free;
JsonFileToObject fails, although the object list is serialized correctly by ObjectToJsonFile. What I am missing here?
Thanks for all your time.
Last edited by salowabicz (2017-12-03 20:02:03)
Offline
Your field is probably not initialized, since the TEnemy.Create constructor is not created.
Inherit from TSynPersistent, and override the virtual constructor.
As I wrote
Adding a ref-counted field in the record is not a good idea, since it will slow down everything.
And the root cause of the problem is that a record published property has no RTTI in the property field of the class, anyway. So adding a ref-counted field won't be enough.
Offline
That did the job. Thank you very much
Offline