Home

Awesome

Json Interceptors

This repository contains diffrent Json Interceptors, which can be useful when working with Json.

EnumMember

When working with Enums and Json, you often recieve a Json value diffrent to the name of you enum. And the other way arround.

An example:

You recieve the following Json

{
	"status": "In Progress"
}

And needs to map it to the following enum:

type
  TSubscriptionPollingStatus = (Success, Pending, InProgress, Error);

In C# you can decorate the Enum type with the EnumMember atteribute like this:

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Runtime.Serialization;


var test = new Test { Status = SubscriptionPollingStatus.InProgress };
//Marshal
var jsonString = JsonConvert.SerializeObject(test);
Console.WriteLine(jsonString == "{\"Status\":\"In Progress\"}");

//Unmarshal
var test2 = JsonConvert.DeserializeObject<Test>(jsonString);
Console.WriteLine(test2?.Status == test.Status);


[JsonConverter(typeof(StringEnumConverter))]
public enum SubscriptionPollingStatus
{
    [EnumMember(Value = "Success")]
    Success,
    [EnumMember(Value = "Pending")]
    Pending,
    [EnumMember(Value = "In Progress")]
    InProgress,
    [EnumMember(Value = "Error")]
    Error
};

class Test
{
    public SubscriptionPollingStatus Status { get; set; } = SubscriptionPollingStatus.Success;
}

But unfortnatly in Delphi you can not decorate the Enum type, or at least the RTTI can not see the attribute, so we have to put the attribute on an Interceptor, and then inject that into an attribute. I know it sounds difficult but with this repo it is easy. Lets look at som code, and port the C# example from above to Delphi:

First lets define a test class:

unit EnumTestClass;

interface

uses
  Json.Interceptors;

type
  TSubscriptionPollingStatus = (Success, Pending, InProgress, Error);

  [EnumMember('Success, Pending, In Progress, Error')]
  TSubscriptionPollingStatusInterceptor = class sealed(TEnumInterceptor<TSubscriptionPollingStatus>)
  end;

  TTest = class
  private
    [StringHandler(TSubscriptionPollingStatusInterceptor)]
    FStatus: TSubscriptionPollingStatus;
  public
    property Status: TSubscriptionPollingStatus read FStatus write FStatus;
  end;

implementation

end.

Note If you put the class in the DPR file, TJson cannot instantiate it. Since it technically speaking becomes a part of the implementaiton part.

Then the demo project:

program EnumDemo;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  REST.Json,
  EnumTestClass in 'EnumTestClass.pas';

begin
  var  test := TTest.Create;
  test.Status := TSubscriptionPollingStatus.InProgress;

  // Marshal
  var  jsonString := TJson.ObjectToJsonString(test);
  Writeln(jsonString = '{"status":"In Progress"}');

  // Unmarshal
  var test2 := TJson.JsonToObject<TTest>(jsonString);
  Writeln(test.Status = test2.Status);

  test.Free;
  test2.Free;
  Readln;
end.

As you see nice an easy :D

If you prefer the syntax to be a little closer to C# you can provide the attributes like this

type
  TSubscriptionPollingStatus = (Success, Pending, InProgress, Error);

  [EnumMember('Success')]
  [EnumMember('Pending')]
  [EnumMember('In Progress')]
  [EnumMember('Error')]
  TSubscriptionPollingStatusInterceptor = class sealed(TEnumInterceptor<TSubscriptionPollingStatus>)
  end;

Finally if you prefer a cleaner syntax, you can ofcause create you own attribute and the just use that:

  PollingStatusAttribute = class(JsonReflectAttribute)
  public
    constructor Create;
  end;
...  
  TEnumClass = class
  private
    [PollingStatus]
    FStatus: TSubscriptionPollingStatus;
  public
    property Status: TSubscriptionPollingStatus read FStatus write FStatus;
  end;
...
{ TPollingStatusAttribute }

constructor PollingStatusAttribute.Create;
begin
  inherited Create(ctString, rtString, TSubscriptionPollingStatusInterceptor, nil, True);
end;

EnumSet

What is a set?

Sets are another way in which Delphi is set apart from other languages. Whereas enumerations allow a variable to have one, and only one, value from a fixed number of values, sets allow you to have any combination of the given values - none, 1, some, or all.

An example:

Type
  TEnum = (One, Two, Three);
  TEnumSet = set of TEnum;

With TEnumSetInterceptor you can do json-serialization and deserialization if a set. Like enumerations you can not decorate the set type definition with attributes, so again we have to put the attribute on an Interceptor.

type
  TEnum = (One, Two, Three);
  TEnumSet = set of TEnum;

  // If you want to use an other seperator, please do so :D
  [SetMembers('One|Two|Three', '|')]
  TEnumSetInterceptor = class(TSetInterceptor<TEnumSet>)
  end;

  TSetClass = class
  private
    [StringsHandler(TEnumSetInterceptor)]
    FEnum: TEnumSet;
  public
    property Enum: TEnumSet read FEnum write FEnum;
  end;

Example of use:

  var SetClass := TSetClass.Create;
  SetClass.Enum := [TEnum.One, TEnum.Three];
  var jsonString := TJson.ObjectToJsonString(SetClass);
  WriteLn(jsonString);

And the generatedJson:

{
	"enum": [
		"One",
		"Three"
	]
}

Ofcause TEnumSetInterceptor supports both marshalling and unmarshalling.

TColorInterceptor

TColorInterceptor is just a simple Interceptor for marshalling and unmarshalling a Color.

You can marshal from the following formats:

{"color":"0x000000FF"}
{"color":"$000000FF"}
{"color":"clRed"}
{"color":"Red"}

When unmarshalling you'll allways get the following format:

{"color":"0x000000FF"}

You can ofcause change that you self. Simply change the implementation of TColorInterceptor.StringConverter

Current implementation:

function TColorInterceptor.StringConverter(Data: TObject; Field: string): string;
var
  Color: TColor;
begin
  Color := GetValue(Data, Field).AsInteger;
  Result := '0x' + IntToHex(Color);
end;

Example of use:

First we need a test class:

unit ColorTestClass;

interface

uses
  Json.Interceptors, System.UITypes;

type
  TColorClass = class
  private
    [StringHandler(TColorInterceptor)]
    FColor: TColor;
  public
    property Color: TColor read FColor write FColor;
  end;

implementation

end.

Then we need to call it:

program ColorDemo;

{$APPTYPE CONSOLE}

{$R *.res}


uses
  REST.Json, System.UITypes,
  ColorTestClass in 'ColorTestClass.pas';

begin
  var  test := TColorClass.Create;
  test.Color := TColors.Red;

  // Marshal
  var  jsonString := TJson.ObjectToJsonString(test);
  Writeln(jsonString = '{"color":"0x000000FF"}');

  // Unmarshal
  var test2 := TJson.JsonToObject<TColorClass>(jsonString);
  Writeln(test.Color = test2.Color);

  test.Free;
  test2.Free;
  Readln;
end.