access all elements of a record using RTTI
Asked Answered
M

2

6

I want to dump a complex / long record into a memo for debugging purpose

 TmyRecord =
     aValue : String 
     aNumber : Real;
     Morenumbers   :  Integer ;
     ....
     ....
  end;

I think Delphi XE 2 RTTI should give me the chance to get the Fieldname , Fieldtype and value within a loop, to write this record to a memo or .....

Moonshine answered 23/5, 2014 at 7:47 Comment(2)
How complex do you need to go? Only simple types in the record? Nested records? Variant records? Complex types of arbitrary nature? Have you any experience at all of RTTI? There's no evidence that you've tried anything. There are plenty of examples on the web. You could do worse than dump it to JSON using one of the many JSON libs. Which leads you certainly to much fine example code.Bethezel
procedure EnumerateFieldandValues(const AObject: TObject; RecordParams: TStringList); var i: Integer; rtype: TRTTIType; fields: TArray<TRttiField>; begin rtype := TRttiContext.Create.GetType(TypeInfo(aObject.ClassType)); // Memo1.Lines.Add(rtype.ToString); fields := rtype.GetFields; for i := 0 to High(fields) do RecordParams.Add(Format('%s: %s :: %s', [fields[i].Name, fields[i].FieldType.ToString, fields[i].GetValue(@m).ToString])); end;Moonshine
I
20

As starting point - record with simple types. For complex fields (array, class etc) explore RTTI unit

type
  TmyRecord = record
    aValue: String;
    aNumber: Real;
    Morenumbers: Integer;
  end;
var
  m: TMyRecord;
  rtype: TRTTIType;
  fields: TArray<TRttiField>;
  i: Integer;
begin
  m.aValue := 'OK';
  m.aNumber := Pi;
  m.Morenumbers := 666;
  rtype := TRTTIContext.Create.GetType(TypeInfo(TMyrecord));
  Memo1.Lines.Add(rtype.ToString);
  fields := rtype.GetFields;
  for i := 0 to High(fields) do
    Memo1.Lines.Add(Format('%s: %s :: %s', [
      fields[i].Name,
      fields[i].FieldType.ToString,
      fields[i].GetValue(@m).ToString]));

output:

TmyRecord
aValue: string :: OK
aNumber: Real :: 3.14159265358979
Morenumbers: Integer :: 666
Incite answered 23/5, 2014 at 8:21 Comment(0)
A
2

Here is my try at this. I have similar task as you (refer to this thread). It's work in progress, but does the job good enough so far. This would enumerate all properties inside the TObject thou, so you'll have to adapt it to enumerate records:

function EnumerateProperties(const AObject: TObject): String;
var
  rt: TRttiType;
  prop: TRttiProperty;
  value, value2: TValue;
  valstr: String;
  propstr: String;
  fullstr: String;
  bres: Boolean;
  meth: TRttiMethod;
  bytes: TBytes;
  bytes_arr: TArray<TBytes>;
  uints: TArray<UINT32>;
  C1: Integer;
begin
  if not Assigned(AObject) then
    Exit('');

  rt := TRttiContext.Create.GetType(AObject.ClassType);

  fullstr := '';
  // iterate through public properties
  for prop in rt.GetDeclaredProperties do
  begin
    value := prop.GetValue(AObject); // get property value
    valstr := '?';

    // check property type
    case prop.PropertyType.TypeKind of
      tkInteger,
      tkInt64,
      tkFloat: valstr := value.AsVariant;

      tkString,
      tkChar,
      tkWChar,
      tkLString,
      tkWString,
      tkUString: valstr := QuotedStr(value.AsString);

      tkEnumeration: begin
        valstr := 'ENUM';
        if value.TryAsType<Boolean>(bres) then
          valstr := BoolToStr(bres, TRUE)
        else
        begin
          valstr := GetEnumName(value.TypeInfo, prop.GetValue(AObject).AsOrdinal);
        end;
      end;

      tkClass: begin
        // check if property is TList or any of its descendants,
        // then iterate through each of it's members
        meth := prop.PropertyType.GetMethod('ToArray');
        if Assigned(meth) then
        begin
          value2 := meth.Invoke(value, []);
          Assert(value2.IsArray);
          for C1 := 0 to value2.GetArrayLength - 1 do
            valstr := valstr + Format('(%s), ', [EnumerateProperties(value2.GetArrayElement(C1).AsObject)]);
          if valstr <> '' then
            Delete(valstr, Length(valstr) - 1, 2);
          valstr := Format('[%s]', [valstr]);
        end
        else // otherwise, process it as normal class
          valstr := Format('[%s]', [EnumerateProperties(value.AsObject)]);
      end;

      // dynamic arrays
      tkDynArray: begin
        if value.TryAsType<TBytes>(bytes) then // TBytes
          valstr := BytesToHex(bytes)
        else
          if value.TryAsType<TArray<TBytes>>(bytes_arr) then // TArray<TBytes>
          begin
            valstr := '';
            for C1 := Low(bytes_arr) to High(bytes_arr) do
              valstr := valstr + QuotedStr(BytesToHex(bytes_arr[C1])) + ', ';
            if valstr <> '' then
              Delete(valstr, Length(valstr) - 1, 2);
            valstr := Format('(%s)', [valstr]);
          end
          else
            if value.TryAsType<TArray<UINT32>>(uints) then // TArray<UINT32>
            begin
              valstr := '';
              for C1 := Low(uints) to High(uints) do
                valstr := valstr + IntToStr(uints[C1]) + ', ';
              if valstr <> '' then
                Delete(valstr, Length(valstr) - 1, 2);
              valstr := Format('(%s)', [valstr]);
            end;
      end;

      tkUnknown: ;
      tkSet: ;
      tkMethod: ;
      tkVariant: ;
      tkArray: ;
      tkRecord: ;
      tkInterface: ;
      tkClassRef: ;
      tkPointer: ;
      tkProcedure: ;
    end;

    propstr := Format('%s: %s', [prop.Name, valstr]);
    fullstr := fullstr + propstr + '; ';
  end;

  if fullstr <> '' then
    Delete(fullstr, Length(fullstr) - 1, 2);

  result := fullstr;
end;
Alerion answered 23/5, 2014 at 9:18 Comment(5)
There seems to be a problem with records and RTTI - see this recent question: #23446275Olnek
my delphi does not know type tkInteger ... -> what unit to include ?Moonshine
tkintger -> found typinfo;Moonshine
@Olnek That question talks about properties rather than fieldsBethezel
@David Heffernan, sure, but the OP didn't seem to exclude properties, judging by the ellipsis.Olnek

© 2022 - 2024 — McMap. All rights reserved.