1

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.

2 Answers 2

1

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.
Sign up to request clarification or add additional context in comments.

Comments

1

Spring4D offers the non generic ICollection interface that provides the items inside of the collection as TValue and has the possibility to add elements via its Add method.

You either have to write your own code around the SerializeInternal and DeserializeInternal methods or integrate the Spring.Collections support directly into the DelphiJSON unit if you want to support collections in other objects - it then needs another check for value.Kind = tkInterface and a Supports check on ICollection.

Here is just a quick example of how to serialize a collection:

function Ser(const coll: ICollection): TJSONValue;
var
  context: TSerContext;
  value: TValue;
begin
  context := TSerContext.Create;
  context.settings := TDJSettings.Default;
  try
    Result := TJSONArray.Create;
    for value in coll do
      TJSONArray(Result).AddElement(SerializeInternal(value, context));
  finally
    context.Free;
  end;
end;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.