Spring4D Nullable<T> JSON serialization

228 Views Asked by At

Is there a way to get TJson.ObjectToJsonString() properly serialize a TNullableInteger field in an object?

I tried to use an attribute on the field using a JsonReflectAttribute with a TJSONInterceptor, but then the integer value, if present, was serialized as string.

3

There are 3 best solutions below

6
Stefan Glienke On

JSON serialization that comes with Delphi is a disaster - as already mentioned in the comment to your question the design of REST.Json prevents any customization of record serializion from what I can see.

The only way known to me is to use a TJsonSerializer from System.JSON.Serializers and add a custom converter that can handle Nullable<T> quite easily:

uses
  Spring,
  System.JSON.Serializers,
  System.JSON.Readers,
  System.JSON.Writers,
  System.Rtti;

type
  TNullableConverter = class(TJsonConverter)
  public
    function CanConvert(ATypeInf: PTypeInfo): Boolean; override;
    function ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo;
      const AExistingValue: TValue; const ASerializer: TJsonSerializer): TValue; override;
    procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue;
      const ASerializer: TJsonSerializer); override;
  end;

function TNullableConverter.CanConvert(ATypeInf: PTypeInfo): Boolean;
begin
  Result := IsNullable(ATypeInf);
end;

function TNullableConverter.ReadJson(const AReader: TJsonReader;
  ATypeInf: PTypeInfo; const AExistingValue: TValue;
  const ASerializer: TJsonSerializer): TValue;
begin
  Result := AExistingValue;
  Result.SetNullableValue(AReader.Value.Convert(GetUnderlyingType(ATypeInf), ISO8601FormatSettings));
end;

procedure TNullableConverter.WriteJson(const AWriter: TJsonWriter;
  const AValue: TValue; const ASerializer: TJsonSerializer);
var
  LTypeInfo: PTypeInfo;
  LValue: TValue;
begin
  LTypeInfo := GetUnderlyingType(AValue.TypeInfo);
  LValue := AValue.GetNullableValue;

  if LValue.IsEmpty then
    AWriter.WriteNull
  else
    if (LTypeInfo = TypeInfo(TDate)) or (LTypeInfo = TypeInfo(TTime)) then
      AWriter.WriteValue(LValue.Convert<string>(ISO8601FormatSettings))
    else
      AWriter.WriteValue(LValue);
end;
3
Alexey Martynov On

Could you write here your code of

TNullableSerializer

=== Thank you Mr.Nicko for the assistance.

I find Mr. Glienke's library very useful and use it in my projects but has lack of knowledge to do it.

0
Nicko On
type
  TNullableSerializer = class(TJsonSerializer)
  strict private
    FNullableConverter: TJsonConverter;

  public
    constructor Create();
    destructor Destroy(); override;
  end;

constructor TNullableSerializer.Create();
begin
  inherited Create();
  Self.ContractResolver := TJsonDefaultContractResolver.Create(TJsonMemberSerialization.Public);

  Self.FNullableConverter := TNullableConverter.Create();
  Self.Converters.Add(Self.FNullableConverter);
end;

destructor TNullableSerializer.Destroy();
begin
  Self.FNullableConverter.Free();
  inherited Destroy();
end;

type
  TNullableConverter = class(TJsonConverter)
  strict private
    FJsonNullValueHandling: TJsonNullValueHandling;

  public
    constructor Create(AJsonNullValueHandling: TJsonNullValueHandling = TJsonNullValueHandling.Include);

    function CanConvert(ATypeInf: PTypeInfo): Boolean; override;
    function ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue;
      const ASerializer: TJsonSerializer): TValue; override;
    procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); override;
  end;

constructor TNullableConverter.Create(AJsonNullValueHandling: TJsonNullValueHandling);
begin
  inherited Create();
  Self.FJsonNullValueHandling := AJsonNullValueHandling;
end;

function TNullableConverter.CanConvert(ATypeInf: PTypeInfo): Boolean;
begin
  Result := IsNullable(ATypeInf);
end;

function TNullableConverter.ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue;
  const ASerializer: TJsonSerializer): TValue;
begin
  Result := AExistingValue;
  Result.SetNullableValue(AReader.Value.Convert(GetUnderlyingType(ATypeInf), ISO8601FormatSettings));
end;


procedure TNullableConverter.WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer);
begin
  var LTypeInfo := Spring.GetUnderlyingType(AValue.TypeInfo);
  var LValue := AValue.GetNullableValue();

  if (LValue.IsEmpty) then
  begin
    if (Self.FJsonNullValueHandling = TJsonNullValueHandling.Include) then
    begin
      AWriter.WriteNull();
    end;
  end
  else
  begin
    if (LTypeInfo = TypeInfo(TDate)) or (LTypeInfo = TypeInfo(TTime)) then
    begin
      AWriter.WriteValue(LValue.Convert<string>(ISO8601FormatSettings));
    end
    else
    begin
      AWriter.WriteValue(LValue);
    end;
  end;
end;