RTTI SetValue on Nullable<T>

Hello Wagner,

I have a global function that allows me to dynamically set values ​​in my objects.

class function TDBTools.Patch<T>( AEntity: T; AJson: TJSONObject): T;
var
  i: Integer;
begin
  for i := 0 to AJson.Count do
    TsngRtti.SetValue( AEntity, 'F'+AJson.Pairs[i].JsonString.Value, AJson.Pairs[i].JsonValue);
  Result := AEntity;
end;

class function TsngRtti.SetValue( AInstance: TObject; AFieldname: String; AFieldValue: TJSONValue): Boolean;
var
  LField: TRttiField;
  LValue: TValue;
begin
  Result := False;
  if AInstance.TryGetField( AFieldname, LField) then begin
    LValue := ConvertJSONValueToValue( AFieldValue, LField.FieldType);
    if not LValue.IsEmpty then
      LField.SetValue( AInstance, LValue);
  end;
end;

The ConvertJSONValueToValue function is my own function where I convert JSON to TValue.
But now I have some strings that are nullable.
And nullable doesn't work with TRttiField.SetValue.
Last line: LField.SetValue( AInstance, LValue);

What can I do?
As short and simple as possible.

Thomas

P.S: In the ConvertJSONValueToValue function I can make case distinctions and recognize the Nullable type. But what happens next?

I have a solution:

class function TsngRtti.SetValue( AInstance: TObject; AFieldname: String; AFieldValue: TJSONValue): Boolean;
var
  LField: TRttiField;
  LValue: TValue;
  NewData: Nullable<String>;
begin
  Result := False;
  if AInstance.TryGetField( AFieldname, LField) then begin
    if copy( LField.FieldType.Name, 1, 8) = 'Nullable' then
      Result := SetNullableValue( AInstance, LField, AFieldValue)
    else begin
      LValue := ConvertJSONValueToValue( AFieldValue, LField.FieldType);
      Result := not LValue.IsEmpty;
      if Result then
        LField.SetValue( AInstance, LValue);
    end;
  end;
end;

and

resourcestring
  _Nullable_string    = 'Nullable<System.string>';
  _Nullable_Integer   = 'Nullable<System.Integer>';
  _Nullable_TDateTime = 'Nullable<System.TDateTime>';
  _Nullable_TGUID     = 'Nullable<System.TGUID>';
  _Nullable_Boolean   = 'Nullable<System.Boolean>';
  _Nullable_Double    = 'Nullable<System.Double>';
  _Nullable_Int64     = 'Nullable<System.Int64>';

class function TsngRtti.SetNullableValue( AInstance: TObject; AField: TRttiField; AFieldValue: TJSONValue): Boolean;
var
  LValue: TValue;
  LName: String;
  LNewString    : Nullable<String>;
  LNewTGuid     : Nullable<TGUID>;
  LNewInteger   : Nullable<Integer>;
  LNewTDateTime : Nullable<TDateTime>;
  LNewDouble    : Nullable<Double>;
  LNewInt64     : Nullable<Int64>;
  LNewBoolean   : Nullable<Boolean>;
begin
  Result := False;
  LValue := AField.GetValue(AInstance);
  LName := AField.FieldType.Name;
  if LName = _Nullable_string then begin
    // String
    LNewString := LValue.AsType<Nullable<String>>;
    LNewString.Value := TJSONString( AFieldValue).Value;
    TValue.Make( @LNewString, TypeInfo( Nullable<String>), LValue);
  end else if LName = _Nullable_Integer then begin
    // Integer
    LNewInteger := LValue.AsType<Nullable<Integer>>;
    LNewInteger.Value := TJSONNumber( AFieldValue).AsInt;
    TValue.Make( @LNewInteger, TypeInfo(Nullable<Integer>), LValue);
  end else if LName = _Nullable_TDateTime then begin
    // TDateTime
    LNewTDateTime := LValue.AsType<Nullable<TDateTime>>;
    LNewTDateTime.Value := ISODateToDateTime( TJSONString( AFieldValue).Value);
    TValue.Make( @LNewTDateTime, TypeInfo(Nullable<TDateTime>), LValue);
  end else if LName = _Nullable_TGUID then begin
    // TGUID
    LNewTGUID := LValue.AsType<Nullable<TGUID>>;
    LNewTGUID.Value := DirtyStringToGuid( TJSONString( AFieldValue).Value);
    TValue.Make( @LNewTGUID, TypeInfo(Nullable<TGUID>), LValue);
  end else if LName = _Nullable_Boolean then begin
    // Boolean
    LNewBoolean := LValue.AsType<Nullable<Boolean>>;
    LNewBoolean.Value := TJSONBool( AFieldValue).AsBoolean;
    TValue.Make( @LNewBoolean, TypeInfo(Nullable<Boolean>), LValue);
  end else if LName = _Nullable_Double then begin
    // Double
    LNewDouble := LValue.AsType<Nullable<Double>>;
    LNewDouble.Value := TJSONNumber( AFieldValue).AsDouble;
    TValue.Make( @LNewDouble, TypeInfo(Nullable<Double>), LValue);
  end else if LName = _Nullable_Int64 then begin
    // Int64
    LNewInt64 := LValue.AsType<Nullable<Int64>>;
    LNewInt64.Value := TJSONNumber( AFieldValue).AsInt64;
    TValue.Make( @LNewInt64, TypeInfo(Nullable<Int64>), LValue);
  end else
    Exit;

  AField.SetValue( AInstance, LValue);
  Result := True;
end;

If anyone has a better solution, please write here.

For anyone interested, here is my call in a XData service function:

        // read JSON Entity 
        LJson :=  // Get data from service func parameter 
        // read DB Entity 
        LEntity := ObjectManager.Find<TMyEntity>(  { The ID });
        // patch Data
        TDBTools.Patch<TMyEntity>( LEntity, LJson);
        ObjectManager.Update( LEntity);
        ObjectManager.Flush;

        // Warning: foreign keys, references must be handled separately

1 Like