2

I am migrating some projects from Delphi 10.4 to Delphi 11.3. I am facing some problems with the JSON library:

See the following codes:

First case:

function TestCreate;
var
  LValue: Variant;
  LJSON: TJSONNumber;
begin
  LValue := 100;
  LJSON := TJSONNumber.Create(LValue);
end;

Compiling this, it results in:

  [dcc64 Error] _Unit.pas(74): E2251 Ambiguous overloaded call to 'Create'
  System.JSON.pas(435): Related method: constructor TJSONNumber.Create(const string);
  System.JSON.pas(439): Related method: constructor TJSONNumber.Create(const Double);

Second case:

function TestAddPair;
var
  LValue: Variant;
  LJSON: TJSONObject;
begin
  LValue := 100;
  LJSON := TJSONObject.Create();
  LJSON.AddPair('Test', LValue);
end;

Compiling this, it results in:

  [dcc64 Error] _Unit.pas(77): E2251 Ambiguous overloaded call to 'AddPair'
  System.JSON.pas(661): Related method: function TJSONObject.AddPair(const string; const Boolean): TJSONObject;
  System.JSON.pas(651): Related method: function TJSONObject.AddPair(const string; const Double): TJSONObject;
  System.JSON.pas(636): Related method: function TJSONObject.AddPair(const string; const string): TJSONObject;

Both used to work properly on Delphi 10.4, but on Delphi 11.3 they do not compile. Is there a way to let them compile? I'd prefer not to modify each command that uses a Variant to create/add a JSON.

Federico Pessina
  • 199
  • 1
  • 4
  • 15

3 Answers3

3

A Variant can implicitly convert to both string or Double type without any preference for one of them.

In R104 the constructor taking a string parameter is protected, while in 11.3 it is public.

As Toon mentioned, you may not be able to avoid changing all the calls.

Update: Alternatively you can create class helpers for TJSONNumber and TJSONObject with overrides for Variant. F.i. like this:

type
  TJSONNumberHelper = class helper for TJSONNumber
  public
    constructor Create(const AValue: Variant); overload;
  end;

constructor TJsonNumberHelper.Create(const AValue: Variant);
begin
  inherited Create(Double(AValue));
end;
Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • Isn't it strange that on R104 TJSONNumber.Create had three public overloads that would accept Double, Integer and Int64 but it did compile successfully without casting the variant to the desired type? Variants can also convert to all these types – Federico Pessina Jun 13 '23 at 11:01
  • 1
    Yeah, but all these types can implicitly be converted to double. – Uwe Raabe Jun 13 '23 at 11:10
  • 1
    You can make own descendant class of TJSONNumber and implement `constructor create(AValue : variant)`. Implementation will be something like `case VarType(AValue) of varInteger, byte : result := inherited create(integer(AValue)); ....` After that - you can just replace existing code with `TJSONNumber.Create` -> `TMyJSONNumber.Create` – Oleksandr Morozevych Jun 13 '23 at 13:56
  • 1
    Another approach could be class helpers. That requires to just add a unit to the uses clause. – Uwe Raabe Jun 13 '23 at 14:58
  • Thanks, that's what I was thinking of doing yesterday, I was hoping there was something already working in the delphi libraries. I will implement it by myself. – Federico Pessina Jun 14 '23 at 05:16
1

This is my function to convert from Variant to JSON.

It identifies if the Variant contains a Null, Number, String, Bool or Date, returning the corresponding JSONValue, it also checks if the Variant is an array so it returns its values in a TJSONArray.

function VariantToJSON(Value: Variant): TJSONValue;
  var i: integer;
      JSONArray: TJSONArray;
  function VarToInt(Value: Variant): integer;
  begin
    Result := Value;
  end;
  function VarToFloat(Value: Variant): double;
  begin
    Result := Value;
  end;
  function Item(Value: Variant): TJSONValue;
  begin
    case VarType(Value) of
      varEmpty, varNull, varUnknown:
        Result := TJSONNull.Create;
      varSmallint, varInteger, varShortInt, VarInt64:
        Result := TJSONNumber.Create(VarToInt(Value));
      varSingle, varDouble, varCurrency:
        Result := TJSONNumber.Create(VarToFloat(Value));
      varDate:
        Result := TJSONString.Create(DateToISO8601(Value));
      varBoolean:
        Result := TJSONBool.Create(Value);
      else
        Result := TJSONString.Create(Value);
    end;
  end;
begin
  if not VarIsArray(Value) then
  begin
    Result := Item(Value);
  end
  else
  begin
    JSONArray := TJSONArray.Create;
    for i := 0 to Length(Value) do
    begin
      JSONArray.AddElement(Item(Value[i]));
    end;
    Result := JSONArray;
  end;
end;
Marc Guillot
  • 6,090
  • 1
  • 15
  • 42
0

Another alternative and different approach could be using mORMot, furthermore it's very optimized to use as little memory and very fast JSON serialization / un-serialization.

program Project1;

{$APPTYPE CONSOLE}

uses
  Syncommons;

var
  DoubleNumber : Double;
  VariantNumber : Variant;
  Json : variant;
begin
  DoubleNumber := 100;
  VariantNumber := 101;
  Json := _Obj(['Double' , DoubleNumber ,
                'Variant' , VariantNumber
               ]);
  assert(VariantSaveJson(Json)='{"Double":100,"Variant":101}');
end.
Xalo
  • 164
  • 1
  • 2
  • 13