Search code examples
delphirttidelphi-10.1-berlin

EInsufficientRTTI exception with message 'Insufficient RTTi available to support this operation'


Trying to convert an object to JSON at run time, I'm getting insufficient RTTI Error. The object is:

{$M+}
{$TYPEINFO ON}
{$METHODINFO ON}
{$RTTI EXPLICIT METHODS([vcPublic, vcPublished]) PROPERTIES([vcPublic, vcPublished])}

TMyPacket = class(TObject)
  Private
    FID: TGUID;
    FToIP: string;
    FToPort: integer;
    FSent: boolean;
    FSentAt: TDateTime;
    FAck: boolean;
    FTimeOut: Cardinal;
    FDataToSendSize: UINT64;
    FDataToSend: AnsiString;
  public
    constructor create;
    destructor free;
  published
    property ID: TGUID read FID write FID;
    property ToIP: string read FToIP write FToIP;
    property ToPort: integer read FToPort write FToPort;
    property Sent: boolean read FSent write FSent;
    property SentAt: TDateTime read FSentAt write FSentAt;
    property Ack: boolean read FAck write FAck;
    property TimeOut: Cardinal read FTimeOut write FTimeOut;
    property DataToSend: AnsiString read FDataToSend write FDataToSend;
  end;

later in the code:

var fpacket: TMYPacket;
begin     
fpacket := TVWTCPPacket.create;
//assign value to class properties
memo1.Lines.Text := TJson.ObjectToJsonString(fpacket); //throws error

and getting the error on JSON conversion. Does anyone have any idea on whats going wrong with Delphi Berlin 10.1? I do remember some RTTI issues on XE2 but not sure what is happening above is related to Delphi old bug or not.


Solution

  • Go into System.Rtti and put a breakpoint into TRttiField.GetValue at the line if ft = nil then and put a breakpoint condition ft = nil (if you would put the breakpoint in the next line you would not be able to see what the Name of the field was because of optimization).

    When your debugger stops there you can inspect Self.Name (Ctrl+F7) and it will tell you D4 which is the fourth field of your TGuid. This is because JsonReflect recursively serializes records by serializing their fields. Because D4 is declared as array[0..7] of Byte it does not contain type info (see also).

    To solve this create your own type interceptor for TGUID:

    type
      TGuidInterceptor = class(TJSONInterceptor)
      public
        function StringConverter(Data: TObject; Field: string): string; override;
        procedure StringReverter(Data: TObject; Field: string; Arg: string); override;
      end;
    
    function TGuidInterceptor.StringConverter(Data: TObject;
      Field: string): string;
    var
      ctx: TRttiContext;
    begin
      Result := ctx.GetType(Data.ClassInfo).GetField(Field).GetValue(Data).AsType<TGuid>.ToString;
    end;
    
    procedure TGuidInterceptor.StringReverter(Data: TObject; Field, Arg: string);
    var
      ctx: TRttiContext;
    begin
      ctx.GetType(Data.ClassInfo).GetField(Field).SetValue(Data, TValue.From(TGuid.Create(Arg)));
    end;
    

    and mark the field accordingly:

    [JsonReflect(ctString, rtString, TGuidInterceptor)]
    FID: TGUID;