Redeclare type helper in other unit, Free Pascal failed to compile - freepascal

I have this unit
unit helper;
interface
{$MODE OBJFPC}
{$MODESWITCH TYPEHELPERS}
{$H+}
uses
sysutils;
type
THelloWorldHelper = type helper(TStringHelper) for string
public
function world() : string;
end;
implementation
function THelloWorldHelper.world() : string;
begin
result := self + ' world';
end;
end.
and other unit that redeclare this type helper
unit helperalias;
interface
{$MODE OBJFPC}
{$MODESWITCH TYPEHELPERS}
{$H+}
uses
helper;
type
THelloWorldHelper = helper.THelloWorldHelper;
implementation
end.
and program as follows
program helloworld;
{$MODE OBJFPC}
{$MODESWITCH TYPEHELPERS}
{$H+}
uses
helperalias;
var hello : string;
world :string;
begin
hello:='hello';
world := hello.world();
writeln(world);
end.
When I run
fpc helloworld.pas
It refused to compile with message Error: Objective-C categories and Object Pascal class helpers cannot be used as types.
If in helloword.pas, I replaced helperalias unit with helper, it worked.
I read about helper restriction.
Why is redeclaring type helper prohibited?

Related

How to dynamically ignore property when I'm Serialization object to json using TJson.ObjectToJsonString

I have this class:
unit untPerson;
interface
type TPerson = class
private
fName : string;
fEmail : string;
fAge : integer;
published
property Name : string read fName write fName;
property Email : string read fEmail write fEmail;
property Age : integer read fAge write fAge;
end;
implementation
end.
And i need to serialize to Json using this code
TJson.ObjectToJsonString(objPerson, []);
But i need to skip Age if equal 0.
if objPerson.Age = 0 then
result := '{"name":"Lucas", "email":"lucas#github.com"}'
else
result := '{"name":"Lucas", "email":"lucas#github.com", "age":30}';
How Can I Do?
if objPerson.Age = 0 then
result := '{"name":"Lucas", "email":"lucas#github.com"}'
else
result := '{"name":"Lucas", "email":"lucas#github.com", "age":30}';
You can't dynamically ignore properties by using code: TJson.ObjectToJsonString(objPerson, []);.
You can put empty string by custom TJSONInterceptor, like Remy Lebeau says in comments(https://en.delphipraxis.net/topic/6155-tjson-suppressemptyvalues-for-empty-integer-double-and-class-fields/), but to exclude it from json you need to put option joIgnoreEmptyStrings: TJson.ObjectToJsonString(objPerson, [joIgnoreEmptyStrings]);.
One more way to do it: use custom converter (descendant of TJsonConverter), but problem is that you can’t use TJson.ObjectToJsonString in this case, because it have precompiled code with creating JSONMarshal using specifically TJSONConverter class without possibility of overriding: TJSONMarshal.Create(TJSONConverter.Create, true, CFRegConverters);. So you need to reimplement all chain of call TJSON.ObjectToJsonString -> ObjectToJsonValue -> TJSONConverters.GetJSONMarshaler -> TJSONMarshal.Create(TJSONConverter.Create, true, CFRegConverters);. And after that you must use this custom implementation.
Easiest way – to add custom method directly to class:
TPerson = class
private
fName : string;
fEmail : string;
fAge : integer;
published
function ToJsonString : string; virtual;
class function FromJsonString(const AJsonStr : string) : TPerson;
property Name : string read fName write fName;
property Email : string read fEmail write fEmail;
property Age : integer read fAge write fAge;
end;
In this case – you can use any custom logic, but the same code: TJson.ObjectToJsonString(objPerson, []);.will does not work correctly. You must use these new methods.
And last – you can try to find some other 3rd party JSON serializers.
As an alternative solution, mORMot have this diamond, and you can always use ObjectToJson to serialize very fast any TObject in a centralized way:
program TestJson;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Syncommons, mORMot;
type TPerson = class
private
fName : string;
fEmail : string;
fAge : integer;
public
class procedure ClassWriter(const aSerializer: TJSONSerializer;
aValue: TObject; aOptions: TTextWriterWriteObjectOptions);
published
property Name : string read fName write fName;
property Email : string read fEmail write fEmail;
property Age : integer read fAge write fAge;
end;
{ TPerson }
class procedure TPerson.ClassWriter(const aSerializer: TJSONSerializer;
aValue: TObject; aOptions: TTextWriterWriteObjectOptions);
var Person: TPerson absolute aValue;
begin
if Person.Age=0 then
aSerializer.AddJSONEscape(['Name',Person.Name,
'Email',Person.Email
])
else
aSerializer.AddJSONEscape(['Name',Person.Name,
'Email',Person.Email,
'Age',Person.Age
]);
end;
var Person : TPerson;
begin
TJSONSerializer.RegisterCustomSerializer(TPerson,nil,TPerson.ClassWriter);
Person := TPerson.Create;
try
Person.Name := 'Jon';
Person.Email := 'jon#gmail.com';
Person.Age := 10;
writeln(ObjectToJson(Person)); // Result {"Name":"Jon","Email":"jon#gmail.com","Age":10}
Person.Age := 0;
writeln(ObjectToJson(Person)); // Result {"Name":"Jon","Email":"jon#gmail.com"}
finally
Person.Free;
end;
readln;
end.
Please, find further details in the amazing documentation

Delphi - why does this function work if the class is not created?

Consider this class:
unit Unit2;
interface
type
TTeste = class
private
texto: string;
public
function soma(a, b: integer): string;
end;
implementation
procedure TForm2.Button1Click(Sender: TObject);
var
teste: TTeste;
begin
teste:= nil;
teste.texto:= '';//access violation
showmessage(teste.soma(5, 3));//no access violation
end;
{ TTeste }
function TTeste.soma(a, b: integer): string;
begin
result:= (a+b).ToString;
end;
end.
should it really work? why? the var crashed but the function doesnt, does it works like a class funtion?
This works because you are not attempting to access any fields of the class. The function doesn't require any memory allocation. If the field texto was used in that function, then it would crash (as you see in your other test), because that memory isn't addressed. But here, there is no memory involved. Both a and b (and the function result for that matter) are allocated elsewhere outside of the class. Part of the instantiation process is allocating memory for each and every one of the fields in the object.
This is just coincidental. It's still highly discouraged to actually use something like this. If you feel the need to access the function without instantiation, then you should make it a class function instead.

Generic nested class function in Free Pascal

I tried writing the following to see if I could simulate generic methods in Free Pascal 3 by combining its support for generic class functions and nested classes:
{$mode delphi}
type TFoo = class
public
type TBar<T> = class
class function Min(const A, B: T): T;
end;
end;
class function TFoo.TBar<T>.Min(const A, B: T): T;
begin
if A < B then
Result := A
else
Result := B;
end;
I have tried several syntactical variations, but I can't get it to compile no matter what. In this form, the compiler gives me a fatal error on line 8 (method identifier expected, Syntax error, ";" expected but "<" found).
What is the proper syntax for this, if at all possible?
Correct and working syntax in fpc 3.0.4, at least in mode objfpc (mode delphi not tested):
{$mode objfpc}
type TFoo = class
public
type generic TBar<T> = class
class function Min(const A, B: T): T;
end;
end;
class function TFoo.TBar.Min(const A, B: T): T;
begin
...
end;

Serialize and deserialize a collection to JSON Delphi

I've searched a lot, but cannot find a working solution. Most likely because I'm pretty new to Delphi (and coming from C#).
I have a unit with the following class declaration:
TReplaceField = class
public
Key: string;
Value: string;
constructor Create; overload;
constructor Create(strKey, strValue: string); overload;
end;
Class can also be record, like suggested in the comment.
I want to create a collection of it, be it an array, TList<T>, or whatever suits best in this case, and then serialize it to JSON (to store it) and later deserialze it to loop over the fields.
I've found REST.JSON and TJson.ObjectToJsonString(), which works good for 1 object.
But if I pass an array of TReplaceField, it gives me a compile error:
E2010 Incompatible types: 'TObject' and 'Dynamic array'
When I use a TList at runtime, I get a different error:
type tkPointer is not currently supported
In C#, all of this is pretty easy, like a few lines of code. I'm pretty stuck on how to do it in Delphi (besides creating JSON by hand via string concatenation).
UPDATE:
With the tips from David, I've got the serialization working. Also deserialization of one object works, but not deserialization of a collection.
My class now looks like this:
TReplaceField = class
private
FKey : string;
FValue : string;
published
property Key : string read FKey write FKey;
property Value : string read FValue write FValue;
public
constructor Create; overload;
constructor Create(strKey, strValue: string); overload;
end;
Serialize one object: mmoJson.Lines.Text := TJson.ObjectToJsonString(Field); OK
Deserialize one object: JsonToObject<TReplaceField>(mmoJson.Lines.Text); OK
Serialize collection: mmoJson.Lines.Text := TJson.ObjectToJsonString(FieldList); OK
Deserialize collection: JsonToObject<TReplaceField>(mmoJson.Lines.Text); FAIL, message:
EConversionError with message 'Internal: Cannot instantiate type System.Generics.Collections.TList<uReplaceField.TReplaceFieldRec>'
Using a record instead of class for ReplaceField gives the same results.
Almost there. What am I missing to get it fully working?
type
TReplaceCollect = TList<TReplaceField>;
Add this after-class declaration.

How do I output the contents of a Set in Pascal?

I'm working with simple sets in Pascal, and simply want to output the contents of a set. Everytime I run the code i get the following error message: 'project1.lpr(17,13) Error: Can't read or write variables of this type'.
here is my code:
program Project1;
{$mode objfpc}{$H+}
uses
sysutils;
type TFriends = (Anne,Bob,Claire,Derek,Edgar,Francy);
type TFriendGroup = Set of TFriends;
Var set1,set2,set3,set4:TFriendGroup; x:integer;
begin
set1:=[Anne,Bob,Claire];
set2:=[Claire,Derek];
set3:=[Derek,Edgar,Francy];
writeln(set1);
readln;
end.
Is there a special method/function to output sets?
thanks
Free Pascal allows write/writeln() of enums without explicit typinfo calls.
So
{$mode objfpc} // or Delphi, For..in needs Object Pascal dialect iirc.
var Person :TFriends;
for Person in Set1 do
writeln(Person);
works fine.
Using WriteStr this can also be written to strings. (writestr functions like write/writestr but then to an string. Originally implemented for ISO/Mac dialects)
You can't directly display the set as a string because there is no type information emitted for it. To do so, your set must be a published property of a class.
Once published in a class, you can use the unit TypInfo to display the set as a string, using the function SetToString(). TypInfo is the FPC unit which does all the compiler reflection things.
Short working example of what you try to do:
program Project1;
{$mode objfpc}{$H+}
uses
sysutils, typinfo;
type
TFriends = (Anne,Bob,Claire,Derek,Edgar,Francy);
TFriendGroup = Set of TFriends;
TFoo = class
private
fFriends: TFriendGroup;
published
property Friends: TFriendGroup read fFriends write fFriends;
end;
Var
Foo: TFoo;
FriendsAsString: string;
Infs: PTypeInfo;
begin
Foo := TFoo.Create;
Foo.Friends := [Derek, Edgar, Francy];
//
Infs := TypeInfo(Foo.Friends);
FriendsAsString := SetToString(Infs, LongInt(Foo.Friends), true);
//
Foo.Free;
writeln(FriendsAsString);
readln;
end.
This program outputs:
[Derek,Edgar,Francy]
To go further:
official documentation about SetToString
this blog article