使用 Delphi 解析可为 null 的 TJSONObject

Parsing nullable TJSONObject with Delphi

我正在使用 Delphi XE3。我有一个 JSON 流,其中对象可以为空。即我可以收到:

"user":null

"user":{"userName":"Pep","email":"pep@whosebug.com"}

我想区分这两种情况,我尝试使用以下代码:

var 
  jUserObject: TJSONObject;

jUserObject := TJSONObject(Get('user').JsonValue);
if (jUserObject.Null) 
then begin
  FUser := nil;
end else begin
  FUser := TUser.Create;
  with FUser, jUserObject do begin
    FEmail := TJSONString(Get('email').JsonValue).Value;
    FUserName := TJSONString(Get('userName').JsonValue).Value;
  end;
end;

如果我在 if (jUserObject.Null) then begin 行中放置一个断点并将鼠标悬停在 jUserObject.Null 上,它会显示 jUserObject.Null = True if "user":null 并且显示 jUserObject.Null = False if "user":{"userName":"Pep","email":"pep@whosebug.com"}

但是,如果我使用调试器进入该行,jUserObject.Null 调用以下 XE3 库代码:

function TJSONAncestor.IsNull: Boolean;
begin
  Result := False;
end;

所以我的 if 句子总是得到 False,即使 "user":null.

我想我总是有解决方法来捕获执行 "user":nullGet('email').JsonValue 时引发的异常,以便区分值是否为 null,但这似乎不是好优雅。

如何检测 JSON 对象在 JSON 流中是否具有空值?

您犯了将 JSON 对象与 Delphi 对象混淆的常见错误。 TJSONObject class 仅表示 JSON 个对象,它们永远不会为空,因为 null 不同于 {...}TJSONObject 不是所有 JSON 值的祖先,就像您的代码假设的那样。 TJSONValue 是。

在您知道它是一个对象之前,不要将您的 "user" 值强制转换为 TJSONObject。首先检查 Null 属性, 然后 类型转换。

Get()return一个TJSONPair。当您有 "user":null 时,TJSONPair.JsonValue 属性 将 return 一个 TJSONNull 对象,而不是 TJSONObject 对象。您的代码没有考虑到这种可能性。它假定 JsonValue 始终是 TJSONObject 并且不验证类型转换。

有两种方法可以解决这个问题。

  1. TJSONPair 有自己的 Null 属性 指定其 JsonValue 是否为空值:

    var
      JUser: TJSONPair;
      jUserObject: TJSONObject;
    
    jUser := Get('user');
    if jUser.Null then begin
      FUser := nil;
    end else begin
      // use the 'as' operator for type validation in
      // case the value is something other than an object...
      jUserObject := jUser.JsonValue as TJSONObject;
      ...
    end;
    
  2. 使用 is 运算符在转换之前测试 JsonValue 的 class 类型:

    var
      JUser: TJSONPair;
      jUserObject: TJSONObject;
    
    jUser := Get('user');
    if jUser.JsonValue is TJSONNull then begin
      FUser := nil;
    end
    else if jUser.JsonValue is TJSONObject then begin
      jUserObject := TJSONObject(jUser.JsonValue);
      ...
    end else begin
      // the value is something other than an object...
    end;