运行时类型信息不足异常,消息为“不足的RTTI无法支持此操作”。

4

我正在尝试在运行时将一个对象转换为JSON,但是我遇到了不足的RTTI错误。 这个对象是:

{$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;

代码后面的部分:
var fpacket: TMYPacket;
begin     
fpacket := TVWTCPPacket.create;
//assign value to class properties
memo1.Lines.Text := TJson.ObjectToJsonString(fpacket); //throws error

在JSON转换过程中遇到错误。有人知道 Delphi Berlin 10.1 出了什么问题吗?我记得XE2存在一些RTTI问题,但不确定上述情况是否与Delphi旧缺陷有关。


正如Stefan和Arnaud在下面正确指出的那样,问题出在GUID的直接序列化上。然而,在类内部更改字段类型在我的情况下并没有起作用。所以我拦截了GUID转换器,具体代码可以在下面找到,并且它完美地运行。非常感谢你们的帮助。 - LastManStanding
3个回答

7
进入 System.Rtti 并在 TRttiField.GetValue 的一行中放置一个条件断点 if ft = nil then,并设置断点条件为 ft = nil(如果您把断点放在下一行,由于优化,您将无法看到字段的名称)。
当调试器停在这里时,您可以检查 Self.Name(Ctrl + F7),它将告诉您 D4,这是您的 TGuid 的第四个字段。这是因为 JsonReflect 通过序列化记录的字段来递归地序列化记录。由于 D4 声明为 array[0..7] of Byte,因此它不包含类型信息(也可参考此处)。
要解决此问题,请为 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;

并相应标记该字段:

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

这个很好用,只有一个注意点:如果你想使用JsonReflect自定义属性,它只能在Data.DBXJSONReflect单元中使用,而不能在REST.JSONReflect单元中使用。我曾经因此困扰了一段时间。 - Tuncay Göncüoğlu

0

TGUID类型的字段不会生成RTTI。

因此,为了序列化您的类,您可以使用getter/setter函数,设置一个字符串属性。

例如:

TMyPacket = class(TObject)
  Private
    FIDValue: TGUID;
    FToIP: string;
    FToPort: integer;
    FSent: boolean;
    FSentAt: TDateTime;
    FAck: boolean;
    FTimeOut: Cardinal;
    FDataToSendSize: UINT64;
    FDataToSend: AnsiString;
    procedure SetID(const value: string);
    function GetID: string;
  public
    constructor create;
    destructor free;
    property IDValue: TGUID read FID write FID;
  published
    property ID: string read GetID write SetID;
    property ToIP: string read FToIP write FToIP;
    property ToPort: integer read FToPort write FToPort
  ...

procedure TMyPacket.SetID(const value: string);
begin
  fIDValue := StringToGUID(value);
end;

function TMyPacket.GetID: string;
begin
  result := GUIDToString(fIDValue);
end;

然后序列化应该可以工作了,有一个"ID":字符串字段,您将在IVValue公共属性中获得TGUID值。


我认为这不会起作用,因为JsonReflect是基于字段操作的。 - Stefan Glienke
如果这是事实,那么JsonReflect就令人失望了。顺便说一下,我们在mORMot中的序列化直接支持TGUID发布字段。 - Arnaud Bouchez
我也会对JsonReflect在像TGUID这样的内置类型上有相同的期望。 - Stefan Glienke
你的TJSONInterceptor解决方案是唯一可行的选择,那就... ;) - Arnaud Bouchez

0

检查这些行是否存在于dpr文件中,如果存在,则必须将其删除

{$IFOPT D-}{$WEAKLINKRTTI ON}{$ENDIF}
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接