Ok, here is a template solution that can be adapted for your specific serialization or other use.
Record,, TSerializerperforms the entire serialization task and the result is saved in the list of lines.
To use it, call the method Serialize('state', TValue.From(state),sList);from the instance TSerializer.
, TValue, , , . .
( , XE2, , Delphi-2010 RTTI, )
:
record state:TState
caption:string=Foo
address:Cardinal=175896
dynamic array counters:Word
counters[0]:Word=2
counters[1]:Word=2
end
dynamic array errors:TError
record errors[0]:TError
code:Word=52
message:string=ERR_NOT_AVAILABLE
end
end
end
:
unit SerializerBoilerplate;
interface
uses
System.SysUtils, Classes, RTTI, TypInfo;
Type
TSerializer = record
private
FSumIndent: string;
procedure IncIndent;
procedure DecIndent;
public
procedure Serialize(const name: string; thing: TValue;
sList: TStrings; first: boolean = true);
end;
implementation
procedure TSerializer.IncIndent;
begin
FSumIndent := FSumIndent + ' ';
end;
procedure TSerializer.DecIndent;
begin
SetLength(FSumIndent, Length(FSumIndent) - 2);
end;
procedure TSerializer.Serialize(const name: string; thing: TValue;
sList: TStrings; first: boolean);
type
PPByte = ^PByte;
var
LContext: TRTTIContext;
LField: TRTTIField;
LProperty: TRTTIProperty;
LRecord: TRTTIRecordType;
LClass: TRTTIInstanceType;
LStaticArray: TRTTIArrayType;
LDynArray: TRTTIDynamicArrayType;
LDimType: TRttiOrdinalType;
LArrayIx: array of integer;
LArrayMinIx: array of integer;
LArrayMaxIx: array of integer;
LNewValue: TValue;
i: integer;
procedure IncIx(var ArrayIx, ArrayMinIx, ArrayMaxIx: array of integer);
var
dimIx: integer;
begin
dimIx := Length(ArrayIx) - 1;
repeat
if (ArrayIx[dimIx] < ArrayMaxIx[dimIx]) then
begin
Inc(ArrayIx[dimIx]);
break;
end
else
begin
ArrayIx[dimIx] := ArrayMinIx[dimIx];
Dec(dimIx);
if (dimIx < 0) then
break;
end;
until (true = false);
end;
function IxToString(const ArrayIx: array of integer): string;
var
i: integer;
begin
Result := '';
for i := 0 to High(ArrayIx) do
Result := Result + '[' + IntToStr(ArrayIx[i]) + ']';
end;
function GetValue(Addr: Pointer; Typ: TRTTIType): TValue;
begin
TValue.Make(Addr, Typ.Handle, Result);
end;
begin
if first then
FSumIndent := '';
case thing.Kind of
{ - Number calls }
tkInteger,
tkInt64,
tkPointer:
begin
sList.Add(FSumIndent + name + ':' + thing.TypeInfo.name + '=' +
thing.ToString);
end;
tkEnumeration:
begin
if (thing.TypeInfo = TypeInfo(boolean)) then
begin
sList.Add(FSumIndent + name + ':' + thing.TypeInfo.name + '=' +
BoolToStr(thing.AsBoolean));
end
else begin
end;
end;
tkSet:
begin
end;
{ - Float calls }
tkFloat:
begin
if (thing.TypeInfo = TypeInfo(TDate)) then
begin
sList.Add(FSumIndent + name + ':' + thing.TypeInfo.name + '=' +
DateToStr(thing.AsExtended));
end
else if (thing.TypeInfo = TypeInfo(TTime)) then
begin
sList.Add(FSumIndent + name + ':' + thing.TypeInfo.name + '=' +
TimeToStr(thing.AsExtended));
end
else if (thing.TypeInfo = TypeInfo(TDateTime)) then
begin
sList.Add(FSumIndent + name + ':' + thing.TypeInfo.name + '=' +
DateTimeToStr(thing.AsExtended));
end
else
begin
sList.Add(FSumIndent + name + ':' + thing.TypeInfo.name + '=' +
FloatToStr(thing.AsExtended));
end;
end;
{ - String,character calls }
tkChar,
tkString,
tkLString:
begin
sList.Add(FSumIndent + name + ':' + thing.TypeInfo.name + '=' +
thing.AsString);
end;
tkWString:
begin
sList.Add(FSumIndent + name + ':' + thing.TypeInfo.name + '=' +
thing.ToString);
end;
tkInterface:
begin
end;
tkWChar,
tkUString:
begin
sList.Add(FSumIndent + name + ':' + thing.TypeInfo.name + '=' +
thing.AsString);
end;
tkVariant:
begin
end;
{ - Generates recursive calls }
tkArray:
begin
LStaticArray := LContext.GetType(thing.TypeInfo) as TRTTIArrayType;
SetLength(LArrayIx, LStaticArray.DimensionCount);
SetLength(LArrayMinIx, LStaticArray.DimensionCount);
SetLength(LArrayMaxIx, LStaticArray.DimensionCount);
sList.Add(FSumIndent + 'static array ' + name + ':' +
LStaticArray.ElementType.name);
IncIndent();
for i := 0 to LStaticArray.DimensionCount - 1 do
begin
LDimType := LStaticArray.Dimensions[i] as TRttiOrdinalType;
LArrayMinIx[i] := LDimType.MinValue;
LArrayMaxIx[i] := LDimType.MaxValue;
LArrayIx[i] := LDimType.MinValue;
end;
for i := 0 to LStaticArray.TotalElementCount - 1 do
begin
Serialize(Name + IxToString(LArrayIx),
GetValue( PByte(thing.GetReferenceToRawData) +
LStaticArray.ElementType.TypeSize * i,
LStaticArray.ElementType),
sList,false);
IncIx(LArrayIx, LArrayMinIx, LArrayMaxIx);
end;
DecIndent();
sList.Add(FSumIndent + 'end');
end;
tkDynArray:
begin
LDynArray := LContext.GetType(thing.TypeInfo) as TRTTIDynamicArrayType;
sList.Add(FSumIndent + 'dynamic array ' + name + ':' +
LDynArray.ElementType.name);
IncIndent();
for i := 0 to thing.GetArrayLength - 1 do
begin
Serialize(Name + '[' + IntToStr(i) + ']',
GetValue( PPByte(thing.GetReferenceToRawData)^ +
LDynArray.ElementType.TypeSize * i,
LDynArray.ElementType),
sList,false);
end;
DecIndent();
sList.Add(FSumIndent + 'end');
end;
tkRecord:
begin
sList.Add(FSumIndent + 'record ' + name +':' +thing.TypeInfo.name);
LRecord := LContext.GetType(thing.TypeInfo).AsRecord;
IncIndent();
for LField in LRecord.GetFields do
begin
Serialize(LField.name, LField.GetValue(thing.GetReferenceToRawData),
sList, false);
end;
DecIndent();
sList.Add(FSumIndent + 'end');
end;
tkClass:
begin
sList.Add(FSumIndent + 'object ' + name + ':' + thing.TypeInfo.name);
IncIndent();
LClass := LContext.GetType(thing.TypeInfo).AsInstance;
for LField in LClass.GetFields do
begin
Serialize(LField.name,
// A hack to get a reference to the object
// See https://stackoverflow.com/questions/2802864/rtti-accessing-fields-and-properties-in-complex-data-structures
GetValue(PPByte(thing.GetReferenceToRawData)^ + LField.Offset,
LField.FieldType),
sList,false);
end;
DecIndent();
sList.Add(FSumIndent + 'end');
end;
{ - Not implemented }
tkClassRef: ;
tkMethod: ;
tkProcedure: ;
tkUnknown: ;
end;
end;
end.
:
program SerializerProj;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Classes,
SysUtils,
RTTI,
SerializerBoilerplate;
Type
TMyObj = Class
private
fI: integer;
fS: string;
end;
TInnerRec = record
A, B, C: string;
end;
TDim1 = 1 .. 3;
TDim2 = 2 .. 5;
TMyArr = array [TDim1, TDim2] of integer; // Must be typed dimensions
TTestRec = record
s: string;
ws: WideString;
st: ShortString;
ansiCh: AnsiChar;
ansiS: AnsiString;
wChar: Char;
B: boolean;
i: integer;
t: TTime;
d: TDate;
dt: TDateTime;
fd: Double;
fS: Single;
r: TInnerRec;
arr: TMyArr;
dArr: array of string;
o: TMyObj;
end;
TError = record
code: Word;
message: String;
end;
TState = record
caption: String;
address: Cardinal;
counters: TArray<Word>;
errors: TArray<TError>;
end;
var
tr: TTestRec;
state: TState;
sList: TStringList;
s: string;
Serializer: TSerializer;
begin
state.caption := 'Foo';
state.address := 175896;
SetLength(state.counters,2);
state.counters[0] := 2;
state.counters[1] := 2;
SetLength(state.errors,1);
state.errors[0].code := 52;
state.errors[0].message := 'ERR_NOT_AVAILABLE';
tr := Default (TTestRec);
sList := TStringList.Create;
try
tr.s := 'A';
tr.ws := 'WS';
tr.st := '[100]';
tr.ansiCh := '@';
tr.ansiS := '@!';
tr.wChar := 'Ö';
tr.B := true;
tr.i := 100;
tr.t := Now;
tr.d := Now;
tr.dt := Now;
tr.fd := Pi;
tr.fS := 2 * Pi;
tr.r.A := 'AA';
tr.r.B := 'BB';
tr.r.C := 'CC';
tr.arr[1, 2] := 12;
tr.arr[1, 3] := 13;
tr.arr[1, 4] := 14;
tr.arr[1, 5] := 15;
tr.arr[2, 2] := 22;
tr.arr[2, 3] := 23;
tr.arr[2, 4] := 24;
tr.arr[2, 5] := 25;
tr.arr[3, 2] := 32;
tr.arr[3, 3] := 33;
tr.arr[3, 4] := 34;
tr.arr[3, 5] := 35;
SetLength(tr.dArr, 3);
tr.dArr[0] := 'A';
tr.dArr[1] := 'B';
tr.dArr[2] := 'C';
tr.o := TMyObj.Create;
tr.o.fI := 11;
tr.o.fS := '22';
Serializer.Serialize('tr', TValue.From(tr), sList);
for s in sList do
WriteLn(s);
sList.Clear;
Serializer.Serialize('state', TValue.From(state),sList);
for s in sList do
WriteLn(s);
ReadLn;
finally
sList.Free;
end;
end.
Rtti .