Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing nullable TJSONObject with Delphi

Tags:

json

delphi

I'm using Delphi XE3. I have a JSON stream where an object can be null. That is, I can receive:

"user":null

or

"user":{"userName":"Pep","email":"[email protected]"}

I want to discriminate both cases, and I tried with this code:

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 I put a breakpoint right in line if (jUserObject.Null) then begin and I mouse over jUserObject.Null it says jUserObject.Null = True if "user":null and it says jUserObject.Null = False if "user":{"userName":"Pep","email":"[email protected]"}

However, if I step into that line with the debugger, jUserObject.Null calls the following XE3 library code:

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

So I always get a False for my if sentence, even if "user":null.

I suppose I always have the workaround of catching the exception that is raised when "user":null and Get('email').JsonValue is executed in order to discriminate if the value is null or not, but that does not seem so elegant.

How is one supposed to detect if an JSON object has a null value in the JSON stream?

like image 299
Pep Avatar asked Jan 08 '23 19:01

Pep


2 Answers

Get() returns a TJSONPair. When you have "user":null, the TJSONPair.JsonValue property will return a TJSONNull object, not a TJSONObject object. Your code is not accounting for that possibility. It assumes the JsonValue is always a TJSONObject and not validating the type-cast.

There are two ways to handle this.

  1. TJSONPair has its own Null property that specifies whether its JsonValue is a null value or not:

    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. Use the is operator to test the class type of the JsonValue before casting it:

    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;
    
like image 197
Remy Lebeau Avatar answered Jan 14 '23 09:01

Remy Lebeau


You've made the common mistake of confusing JSON objects with Delphi objects. The TJSONObject class represents JSON objects only, which are never null because null is distinct from {...}. TJSONObject is not the ancestor for all JSON values, like your code assumes. TJSONValue is.

Don't type-cast your "user" value to TJSONObject until you know it's an object. Check the Null property first, then type-cast.

like image 43
Rob Kennedy Avatar answered Jan 14 '23 10:01

Rob Kennedy