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 oftest1and
test2to"Godzilla",test3to"Tiburon"
The solution is in his repository: github/chuacw/EnumJson:
- solution: EnumJSON.Interceptors.pas
- example: EnumDemo.pas / CustomizedEnumJSON.dpr
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:
- derive a generic
TEnumInterceptor<T>from [Archive.is]Data.DBXJSONReflect.TJSONInterceptorand put the conversion logic in there - attach attributes for each enumeration value to descendants of
TEnumInterceptor<T>because of a compiler limitation and bug - bind those descendants to the properties in the class to be marshaled using the [Archive.is]
Data.DBXJSONReflect.JSONReflectAttribute
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.is] Made 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 [WayBack] JSON 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:
- Spring4D in Source/Base/Spring.SystemUtils.pas has a
TEnumclass with static methods - David Heffernan has a
TEnumerationclass with static methods - Agustin Ortu has an
Enum.Wrapperunit with various records mimicing .NET behaviour based on David’s work - Attribute mapping in [WayBack] I want to annotate some of my enumerated types with human facing names… – David Heffernan – Google+
- Some commercial 3rd party libraries also offer extra enumeration support.
Some more thoughts from a different perspective: [WayBack] What’s New in Edge Rails: Active Record enums
–jeroen
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |






Leave a comment