Alex Egorov
Alex Egorov

Reputation: 937

Generic helper for record

Currently I have many records with helper methods like this:

TRec1 = packed record
  S1: string;
  S2: string;
  procedure FromJSON(const AJSON: string);
  function  ToJSON: string;
end;

procedure TRec1.FromJSON(const AJSON: string);
begin
  RecordLoadJSON(Self, StringToUTF8(AJSON), TypeInfo(TRec1));
end;

function TRec1.ToJSON: string;
begin
  Result := UTF8ToString(RecordSaveJSON(Self, TypeInfo(TRec1)));
end;

TRec2 = packed record
  S1: string;
  I1: string;
  procedure FromJSON(const AJSON: string);
  function  ToJSON: string;
end;

procedure TRec2.FromJSON(const AJSON: string);
begin
  RecordLoadJSON(Self, StringToUTF8(AJSON), TypeInfo(TRec2));
end;

function TRec2.ToJSON: string;
begin
  Result := UTF8ToString(RecordSaveJSON(Self, TypeInfo(TRec2)));
end;

As you can see all records contain the same methods ToJSON and FromJSON. This methods contain completely the same code except TypeInfo()

Any way to use generics for this and do not declate this methods for every records?

Upvotes: 3

Views: 1258

Answers (1)

Johan
Johan

Reputation: 76567

Records or classes?
Even though Delphi has support for methods, records are still 2nd class citizens because there is no support for record inheritance.
Classes have all the features records have plus the full set of OOP features, so they are often a better choice. However you need to deal with the reference semantics that requires managing the creation and destruction of classes manually on non-ARC platforms vs the care-free value semantics of records that have automatic clean-up.

Non generics solution (using a class)
Declare the string containers as a class and use inheritance.

TRec1 = class(TPersistent)
  S1: string;
  S2: string;
  procedure FromJSON(const AJSON: string);
  function  ToJSON: string;
end;

TRec2 = class(TRec1)
end;

procedure TRec1.FromJSON(const AJSON: string);
begin
  RecordLoadJSON(Self, StringToUTF8(AJSON), Self.ClassInfo);
end;

function TRec1.ToJSON: string;
begin
  Result := UTF8ToString(RecordSaveJSON(Self, Self.ClassInfo));
end;

All classes inherited from TPersistent have RTTI and thus the self.classinfo works. You may need to revise the RecordSaveJSON call depending on your needs.

Generics solution
If you have RTTI enabled for your unit {$M+} and you must use records, simply use methods outside of the record and feed the record type as a generic parameter.

TRec1 = record
  data: typex;
  ....
end;

TDoJSONThings = record     //helper record for any and all types with RTTI.
  procedure FromJSON<T:record>(var Data: T; const JSON: string); static;
  function ToJSON<T:record>(const [ref] Data: T): string; static;
end;

procedure TDOJSONThings.FromJSON<T>(var Data: T; const JSON: string); static;
var
  pt: PTypeInfo;
begin
  pt:= TypeInfo(T);
  RecordLoadJSON(Data, StringToUTF8(AJSON), pt);
end;

function TDOJSONThings.ToJSON<T:record>(const [ref] Data: T): string; static;
var
  pt: PTypeInfo;
begin
  pt:= TypeInfo(T);
  Result:= UTF8ToString(RecordSaveJSON(Data, pt));
end;

Remarks
Using a record helper (as in THelper = record helper for TRec1) will not work, because that will only work for TRec1 and records sadly do not support inheritance.

It would have been better to use stand-alone methods, but Delphi does not allow generic methods that are not part of a record or class.

Oh and please drop the 1970's packed record style construct. It serves no purpose but to make your code slower by misaligning it.

Upvotes: 2

Related Questions