The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 2,162 other followers

How to convert a Delphi enum or set to a JSON value, with different specific values…

Posted by jpluimers on 2019/11/07

As I will probably need this one day: [WayBack] How does one convert a Delphi enum to a JSON value, with different specific values? – CHUA Chee Wee – Google+:

eg, TEnum1 = (test1, test2, test3)

TSomeClass.FEnum := test1;

When converted to JSON, I’d like to see maybe
{"Enum": "Value1"} instead of test1

and test2 to "Godzilla", test3 to "Tiburon"

The solution is in his repository: github/chuacw/EnumJson:

After initially suggesting to look into [Archive.is] Serializing User Objects – RAD Studio, he based his solution on a set of clever tricks circumventing Delphi compiler limitations and bugs:

Later he extended the solution to include sets in additions to enums: [Archive.is] Persisting enumeration and sets to JSON – Chee Wee’s blog: IT solutions for Singapore and companies worldwide (via [Archive.isMade my JSON interceptor demo public. Now you can save your enum and sets to JSON, with customized output to boot! – CHUA Chee Wee – Google+)

Clever!

Hopefully this got Lars Fosdal some ideas to solve [WayBackJSON in Berlin – Can I persuade TJSon to treat “params” as it was a string and not an object for the TJsonRPC class? – Lars Fosdal – Google+

Enums with Delphi

Enums with Delphi and their mapping is a repeating topic, see for instance [WayBack] What’s the easiest (ie., least coding) way to map an enum to a const string and vice versa? (Can attributes be used on enum values yet?) eg., type TMyColor… – David Schwartz – Google+

It shows how much the Delphi language is in need of language enhancements as right now there are way too many open source libraries struggling with the same issues each working around them or providing solutions in slightly different way.

Three things immediately come to mind:

  • the NameOf that – analogous to TypeOf – gets the name of an identifier as a string
  • expose all RTTI for all enum types (especially the ones with non-contiguous values or starting at another value than ordinal zero)
  • allow for attributes on enum values

Some examples of libraries providing enumeration support:

Some more thoughts from a different perspective: [WayBackWhat’s New in Edge Rails: Active Record enums

–jeroen

unit Enum.Wrapper;
interface
uses
System.SysUtils,
System.TypInfo;
type
EEnumOutOfRange = class(System.SysUtils.EArgumentOutOfRangeException);
EEnumParseError = class(System.SysUtils.Exception);
{$REGION 'EnumWrapper<T>'}
EnumWrapper<T: record {: enum}> = record
strict private
FEnumValue: T;
constructor Create(const Value: T);
public
class operator Implicit(const Value: T): EnumWrapper<T>; inline;
class operator Implicit(const Value: Integer): EnumWrapper<T>; inline;
class operator Implicit(const Value: string): EnumWrapper<T>; inline;
class operator Implicit(const Value: EnumWrapper<T>): string; inline;
class operator Implicit(const Value: EnumWrapper<T>): Integer; inline;
class operator Implicit(const Value: EnumWrapper<T>): T; inline;
function ToInteger: Integer; inline;
function ToString: string; inline;
property Value: T read FEnumValue;
end;
{$ENDREGION}
{$REGION 'TEnumEnumerator'}
TEnumEnumerator<T: record {: enum}> = record
private
FCurrentIndex: Integer;
function GetCurrent: T;
public
constructor Create(const Dummy: Integer);
function MoveNext: Boolean;
property Current: T read GetCurrent;
end;
{$ENDREGION}
{$REGION 'TEnumIterator'}
TEnumIterator<T: record {: enum}> = record
public
function GetEnumerator: TEnumEnumerator<T>; inline;
end;
{$ENDREGION}
{$REGION 'Enum<T>'}
Enum<T: record {: enum}> = record
strict private
class function EnumTypeInfo: PTypeInfo; static; inline;
class function EnumTypeData: PTypeData; static; inline;
class procedure ValueOutOfRange(const Value: T; const Namespace, MethodName: string); static;
class procedure IntegerOutOfRange(const Value: Integer; const Namespace, MethodName: string); static;
public
class function Iterate: TEnumIterator<T>; static; inline;
class function TypeName: string; static; inline;
class function ValueName(const Value: T): string; static; inline;
class function Parse(const Ordinal: Integer): EnumWrapper<T>; overload; static; inline;
class function Parse(const EnumValueName: string): EnumWrapper<T>; overload; static; inline;
class function ToInteger(const Value: T): Integer; static; inline;
class function MaxValue: Integer; static; inline;
class function MinValue: Integer; static; inline;
class function InRange(const Value: T): Boolean; overload; static;
class function InRange(const Value: Integer): Boolean; overload; static;
class procedure CheckInRange(const Value: T; const MethodName: string); overload; static;
class procedure CheckInRange(const Value: Integer; const MethodName: string); overload; static;
class function Count: Integer; static;
class function AsArray: TArray<EnumWrapper<T>>; static;
class function First: EnumWrapper<T>; static;
class function Last: EnumWrapper<T>; static;
class procedure StringParseError(const Value: string); static;
end;
{$ENDREGION}
implementation
uses
System.Math;
{$REGION 'Enum<T>'}
class function Enum<T>.InRange(const Value: T): Boolean;
begin
Result := InRange(ToInteger(Value));
end;
class function Enum<T>.InRange(const Value: Integer): Boolean;
begin
Result := System.Math.InRange(Value, Enum<T>.MinValue, Enum<T>.MaxValue);
end;
class function Enum<T>.MaxValue: Integer;
begin
Result := Enum<T>.EnumTypeData.MaxValue;
end;
class function Enum<T>.MinValue: Integer;
begin
Result := Enum<T>.EnumTypeData.MinValue;
end;
class function Enum<T>.Last: EnumWrapper<T>;
begin
Result := Enum<T>.Parse(MaxValue);
end;
class function Enum<T>.First: EnumWrapper<T>;
begin
Result := Enum<T>.Parse(MinValue);
end;
class function Enum<T>.Iterate: TEnumIterator<T>;
begin
// managed type by compiler, no need to create
end;
class function Enum<T>.ToInteger(const Value: T): Integer;
begin
Result := 0;
System.Move(Value, Result, System.SizeOf(Value));
end;
class function Enum<T>.TypeName: string;
begin
{$IFDEF NEXTGEN}
Result := System.TypInfo.GetTypeName(Enum<T>.EnumTypeInfo);
{$ELSE NEXTGEN}
Result := string(Enum<T>.EnumTypeInfo.Name);
{$ENDIF NEXTGEN}
end;
class function Enum<T>.ValueName(const Value: T): string;
begin
Result := System.TypInfo.GetEnumName(Enum<T>.EnumTypeInfo, Enum<T>.ToInteger(Value));
end;
class function Enum<T>.EnumTypeData: PTypeData;
begin
Result := System.TypInfo.GetTypeData(Enum<T>.EnumTypeInfo);
end;
class function Enum<T>.EnumTypeInfo: PTypeInfo;
begin
Result := System.TypeInfo(T);
end;
class procedure Enum<T>.CheckInRange(const Value: T; const MethodName: string);
begin
if not Enum<T>.InRange(Value) then
Enum<T>.ValueOutOfRange(Value, Enum<T>.TypeName, MethodName);
end;
class procedure Enum<T>.CheckInRange(const Value: Integer; const MethodName: string);
begin
if not Enum<T>.InRange(Value) then
Enum<T>.IntegerOutOfRange(Value, Enum<T>.TypeName, MethodName);
end;
class function Enum<T>.Count: Integer;
begin
Result := Enum<T>.MaxValue – Enum<T>.MinValue + 1;
end;
class function Enum<T>.Parse(const Ordinal: Integer): EnumWrapper<T>;
begin
Assert(System.SizeOf(Result) <= System.SizeOf(Ordinal));
System.Move(Ordinal, Result, System.SizeOf(Result));
end;
class function Enum<T>.Parse(const EnumValueName: string): EnumWrapper<T>;
var
Each: T;
begin
for Each in Enum<T>.AsArray do
begin
if Enum<T>.ValueName(Each) = EnumValueName then
Exit(Each);
end;
StringParseError(EnumValueName);
end;
class function Enum<T>.AsArray: TArray<EnumWrapper<T>>;
var
I: Integer;
begin
System.SetLength(Result, Enum<T>.Count);
for I := System.Low(Result) to System.High(Result) do
Result[I] := Enum<T>.Parse(I);
end;
class procedure Enum<T>.StringParseError(const Value: string);
const
SCannotParseString = '%s is not defined in enum %s';
begin
raise EEnumParseError.CreateFmt(SCannotParseString, [Value, TypeName]);
end;
class procedure Enum<T>.ValueOutOfRange(const Value: T; const Namespace, MethodName: string);
begin
IntegerOutOfRange(ToInteger(Value), Namespace, MethodName);
end;
class procedure Enum<T>.IntegerOutOfRange(const Value: Integer; const Namespace, MethodName: string);
const
SEnumOutOfRange = '%s.%s :: %d is out of range for enum %s';
begin
raise EEnumOutOfRange.CreateFmt(SEnumOutOfRange, [Namespace, MethodName, Value, TypeName]);
end;
{$ENDREGION}
{$REGION 'EnumWrapper<T>'}
constructor EnumWrapper<T>.Create(const Value: T);
begin
FEnumValue := Value;
end;
class operator EnumWrapper<T>.Implicit(const Value: T): EnumWrapper<T>;
begin
Result := EnumWrapper<T>.Create(Value);
end;
class operator EnumWrapper<T>.Implicit(const Value: Integer): EnumWrapper<T>;
begin
Result := EnumWrapper<T>.Create(Enum<T>.Parse(Value));
end;
class operator EnumWrapper<T>.Implicit(const Value: string): EnumWrapper<T>;
begin
Result := EnumWrapper<T>.Create(Enum<T>.Parse(Value));
end;
class operator EnumWrapper<T>.Implicit(const Value: EnumWrapper<T>): Integer;
begin
Result := Enum<T>.ToInteger(Value.FEnumValue);
end;
class operator EnumWrapper<T>.Implicit(const Value: EnumWrapper<T>): string;
begin
Result := Enum<T>.ValueName(Value.FEnumValue);
end;
class operator EnumWrapper<T>.Implicit(const Value: EnumWrapper<T>): T;
begin
Result := Value.FEnumValue;
end;
function EnumWrapper<T>.ToInteger: Integer;
begin
Result := Self;
end;
function EnumWrapper<T>.ToString: string;
begin
Result := Self;
end;
{$ENDREGION}
{$REGION 'TEnumIterator'}
function TEnumIterator<T>.GetEnumerator: TEnumEnumerator<T>;
begin
Result := TEnumEnumerator<T>.Create(0);
end;
{$ENDREGION}
{$REGION 'TEnumEnumerator<T>'}
constructor TEnumEnumerator<T>.Create(const Dummy: Integer);
begin
FCurrentIndex := –1;
end;
function TEnumEnumerator<T>.GetCurrent: T;
begin
Result := Enum<T>.Parse(FCurrentIndex);
end;
function TEnumEnumerator<T>.MoveNext: Boolean;
begin
if FCurrentIndex < Enum<T>.Count – 1 then
begin
System.Inc(FCurrentIndex);
Result := True;
end
else
Result := False;
end;
{$ENDREGION}
end.

view raw
Enum.Wrapper.pas
hosted with ❤ by GitHub

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
%d bloggers like this: