jsondelphideserializationspring4d

Delphi and JSON serialization with Spring collections


I am trying to include the Spring4D framework in my latest Delphi project. The collection functions for sorting and filtering are the main features.

However, I have great difficulty to serialize a JSON array to an IList<T> where T is a record.

When I use the default generic.collecions it all gets serialized perfectly. I'm using https://github.com/gruco0002/DelphiJSON for the JSON part because it's serializing records and the standard generic lists.

This is working when using the default generic collections:

result.Data := DelphiJSON<T>.Deserialize(response.Content, jsonSetting);

I hope I don't have to revert back to TDataSets. I have tried to instantiate the IList<T> and add the JSON array myself.


Solution

  • The System.JSON.Converters unit contains converters for standard generics - learn it to make your own converters. To convert the root array you must to add your converter to the serializer. To convert fields you can use the RTTI attribute [JsonConverter(<Your_Own_Converter_Class_Name>)] declared in the System.JSON.Serializers unit.

    Converter example for IList<T>:

    unit Unit1;
    
    interface
    
    uses
      System.SysUtils,
      System.JSON.Serializers,
      System.JSON.Converters,
      System.JSON.Writers,
      System.JSON.Readers,
      Spring.Collections,
      Spring;
    
    type
      TJsonListConverter<V> = class(TJsonConverter)
      protected
        function CreateInstance: IList<V>; virtual;
      public
        procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue;
          const ASerializer: TJsonSerializer); override;
        function ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo;
          const AExistingValue: TValue; const ASerializer: TJsonSerializer): TValue; override;
        function CanConvert(ATypeInf: PTypeInfo): Boolean; override;
      end;
    
    implementation
    
    uses
      System.Rtti,
      System.TypInfo,
      System.JSON.Utils,
      System.JSON.Types,
      System.JSONConsts;
    
    function TJsonListConverter<V>.CanConvert(ATypeInf: PTypeInfo): Boolean;
    begin
      Result := TJsonTypeUtils.IsAssignableFrom(ATypeInf, TypeInfo(IList<V>));
    end;
    
    function TJsonListConverter<V>.CreateInstance: IList<V>;
    begin
      Result := TCollections.CreateList<V>;
    end;
    
    function TJsonListConverter<V>.ReadJson(const AReader: TJsonReader;
      ATypeInf: PTypeInfo; const AExistingValue: TValue;
      const ASerializer: TJsonSerializer): TValue;
    var
      List: IList<V>;
      Arr: TArray<V>;
    begin
      if AReader.TokenType = TJsonToken.Null then
        Result := nil
      else
      begin
        ASerializer.Populate(AReader, Arr);
        if AExistingValue.IsEmpty then
          List := CreateInstance
        else
          List := AExistingValue.AsType<IList<V>>;
        List.AddRange(Arr);
        Result := TValue.From(List);
      end;
    end;
    
    procedure TJsonListConverter<V>.WriteJson(const AWriter: TJsonWriter;
      const AValue: TValue; const ASerializer: TJsonSerializer);
    var
      List: IList<V>;
    begin
      if AValue.TryAsType(List) then
        ASerializer.Serialize(AWriter, List.ToArray)
      else
        raise EJsonException.Create(
          Format(SConverterNotMatchingType,
          [AValue.TypeInfo^.Name, PTypeInfo(TypeInfo(IList<V>))^.Name]));
    end;
    
    end.
    

    Using:

    uses
      System.JSON.Serializers,
      Spring.Collections,
      Spring,
      Unit1 in 'Unit1.pas';
    
    type
      TMyRecord = record
        p1, p2, p3: integer;
      end;
    
    resourcestring
      MyJson =
        '[{"p1":1,"p2":2,"p3":3},{"p1":4,"p2":5,"p3":6},{"p1":7,"p2":8,"p3":9}]';
    
    begin
      // create a serializer
      var Serializer :=
        Shared.Make<TJsonSerializer>(
          TJsonSerializer.Create,
          procedure(const arg: TJsonSerializer)
          begin
            for var Converter in arg.Converters do Converter.Free;
          end
        );
    
      // add converter
      Serializer.Converters.Add(TJsonListConverter<TMyRecord>.Create);
    
      // deserialize
      var MyArray := Serializer.Deserialize<IList<TMyRecord>>(MyJson);
    
      // serialize
      Writeln(Serializer.Serialize(MyArray));
    
      Readln;
    end.