When talking to your REST server or nodeJS service, it can sometimes be overkill to ship a huge and complex wad of JSON or XML over the wire. I noticed that WebSocket transferes can sometimes get a tiny delay if the server is dealing with high payloads. Although we are talking milliseconds here, it’s always good to refactor and make things as small as possible.
The solution I came up with is essentially a dynamically created “record”. Where you would normally write:
type
TMyData = record
field1: String;
field2: TDateTime;
field3: Integer;
end;
TW3Structure allows you to do this programatically, both for binary and JSON. Which can be handy when playing around with your own protocols. So with TW3Structure you can do stuff like:
var Structure := TW3JsonStructure.Create(null);
Structure.WriteString('field1','some value');
Structure.WriteDateTime('field1',Now);
Structure.WriteInt('field2',1200);
var JSONData:String;
Structure.SaveToJSON(JSONData);
WebSocket.Send(JSONData);
I also added support for TStream which does the exact same thing, but in true binary form. Perfect for storing simple data in the browser cache or your mobile device’s sandbox.
unit system.namepair;
interface
uses
System.types,
System.streams,
SmartCL.System;
type
EW3Structure = class(EW3Exception);
TW3Structure = Class(TObject)
public
procedure WriteString(name:string;value:String;
const encode:Boolean);overload;
procedure WriteInt(name:string;Value:Integer);overload;
procedure WriteBool(name:string;value:Boolean);overload;
procedure WriteFloat(name:String;value:Float);overload;
procedure WriteDateTime(name:String;value:TDateTime);overload;
procedure Write(name:String;value:variant);overload;virtual;abstract;
procedure Clear;virtual;abstract;
procedure SaveToJSON(var Data:String);virtual;abstract;
procedure LoadFromJSON(Value:String);virtual;abstract;
procedure SaveToStream(Stream:TStream);virtual;abstract;
procedure LoadFromStream(Stream:TStream);virtual;abstract;
end;
TW3JsonStructure = Class(TW3Structure)
private
FBuffer: Variant;
public
procedure Clear;override;
procedure SaveToJson(var Data:String);override;
procedure LoadFromJson(Value:String);override;
procedure SaveToStream(Stream:TStream);override;
procedure LoadFromStream(Stream:TStream);override;
procedure Write(name:String;value:variant);overload;override;
Constructor Create(const Data:Variant);virtual;
end;
TW3BinProxyItem = Record
daName: String;
daValue: Variant;
end;
TW3BinaryStructure = Class(TW3Structure)
private
FData: Array of TW3BinProxyItem;
function IndexOfName(name:String):Integer;
protected
procedure Clear;override;
procedure SaveToJson(var Data:String);override;
procedure LoadFromJson(Value:String);override;
procedure SaveToStream(Stream:TStream);override;
procedure LoadFromStream(Stream:TStream);override;
procedure Write(name:String;value:variant);overload;override;
end;
implementation
resourcestring
CNT_ERR_STRUCTURE_FUNCTION =
'Stream storage failed, structure contains function reference error';
CNT_ERR_STRUCTURE_SYMBOL =
'Stream storage failed, structure contains symbol reference error';
CNT_ERR_STRUCTURE_UNKNOWN =
'Stream storage failed, structure contains uknown data-type error';
CNT_ERR_STRUCTURE_FAILEDREAD =
'Failed to read structure, invalid datatype error';
CNT_ERR_STRUCTURE_INVALIDSIGN =
'Failed to read structure, invalid data signature error';
//############################################################################
// TW3BinaryStructure
//###########################################################################
const
CNT_STRUCT_SIGNATURE = $CCCCFF00;
function TW3BinaryStructure.IndexOfName(name:String):Integer;
var
x: Integer;
begin
result:=-1;
if FData.Count>0 then
for x:=FData.low to FData.high do
begin
if Sametext(Fdata[x].daName,Name) then
begin
result:=x;
break;
end;
end;
end;
procedure TW3BinaryStructure.Write(name:String;value:variant);
var
mIndex: Integer;
mItem: TW3BinProxyItem;
Begin
mIndex:=IndexOfName(name);
if mIndex<0 then
begin
mItem.daName:=name;
mItem.daValue:=Value;
FData.add(mItem);
end else
FData[mIndex].daValue:=Value;
end;
procedure TW3BinaryStructure.Clear;
Begin
FData.Clear;
end;
procedure TW3BinaryStructure.SaveToJson(var Data:String);
Begin
asm
@Data = json.stringify( (@self.FData) );
end;
end;
procedure TW3BinaryStructure.LoadFromJson(Value:String);
Begin
asm
(@self.FData) = json.parse(@Value);
end;
end;
procedure TW3BinaryStructure.SaveToStream(Stream:TStream);
var
x: Integer;
mWriter: TWriter;
Begin
mWriter:=TWriter.Create(Stream);
try
mWriter.WriteInteger(CNT_STRUCT_SIGNATURE);
mWriter.WriteInteger(FData.Count);
if FData.Count>0 then
begin
for x:=0 to FData.Count-1 do
begin
/* Write the name */
mWriter.WriteString(FData[x].daName);
/* Write the datatype */
mWriter.WriteInteger(ord(FData[x].daValue.datatype));
/* Write the value */
case FData[x].daValue.datatype of
vdBoolean:
begin
mWriter.WriteBoolean(FData[x].daValue);
end;
vdInteger:
begin
mWriter.WriteInteger(FData[x].daValue);
end;
vdFloat:
begin
mWriter.WriteDouble(FData[x].daValue);
end;
vdString:
begin
mWriter.WriteString(FData[x].daValue);
end;
vdFunction:
begin
Raise EW3Structure.Create(CNT_ERR_STRUCTURE_FUNCTION);
end;
vdSymbol:
begin
Raise EW3Structure.Create(CNT_ERR_STRUCTURE_SYMBOL);
end;
vdUnknown:
begin
Raise EW3Structure.Create(CNT_ERR_STRUCTURE_UNKNOWN);
end;
end;
end;
end;
finally
mWriter.free;
end;
end;
procedure TW3BinaryStructure.LoadFromStream(Stream:TStream);
var
mCount: Integer;
mReader: TReader;
mKind: Integer;
mRec: TW3BinProxyItem;
Begin
Clear;
mReader:=TReader.Create(Stream);
try
if mReader.ReadInteger = CNT_STRUCT_SIGNATURE then
begin
mCount:=mReader.ReadInteger;
while mCount>0 do
begin
mRec.daName:=mReader.ReadString;
mKind:=mReader.ReadInteger;
case TW3VariantDataType(mKind) of
vdBoolean: mRec.daValue:=mReader.ReadBoolean;
vdInteger: mRec.daValue:=mReader.ReadInteger;
vdFloat: mRec.daValue:=mReader.ReadDouble;
vdString: mRec.daValue:=mReader.ReadString;
else
Raise EW3Structure.Create(CNT_ERR_STRUCTURE_FAILEDREAD);
end;
FData.add(mRec);
mRec.daName:="";
mRec.daValue:=null;
dec(mCount);
end;
end else
Raise EW3Structure.Create(CNT_ERR_STRUCTURE_INVALIDSIGN);
finally
mReader.free;
end;
end;
//############################################################################
// TW3Structure
//###########################################################################
procedure TW3Structure.WriteString(name:string;
value:String;const encode:Boolean);
begin
(* base64 encode string to avoid possible recursion
should someone store another JSON object as a string *)
if encode then
Begin
asm
@value = btoa(@value);
end;
end;
Write(name,value);
end;
procedure TW3Structure.WriteInt(name:string;Value:Integer);
begin
Write(name,value);
end;
procedure TW3Structure.WriteBool(name:string;value:Boolean);
begin
Write(name,value);
end;
procedure TW3Structure.WriteFloat(name:String;value:Float);
begin
Write(name,value);
end;
procedure TW3Structure.WriteDateTime(name:String;value:TDateTime);
begin
Write(name,value);
end;
//############################################################################
// TW3JsonStructure
//###########################################################################
Constructor TW3JsonStructure.Create(const Data:Variant);
begin
inherited Create;
if not TVariant.IsNull(Data)
and not Data.IsUnassigned then
FBuffer:=Data else
FBuffer:=TVariant.CreateObject;
end;
procedure TW3JsonStructure.SaveToJSON(var Data:String);
begin
if (FBuffer<>unassigned)
and (FBuffer<>NULL) then
Data:=JSON.Stringify(FBuffer) else
Data:=null;
end;
procedure TW3JsonStructure.LoadFromJSON(Value:String);
begin
value:=trim(Value);
if value.length>0 then
FBuffer:=JSON.Parse(value) else
FBuffer:=TVariant.CreateObject;
end;
procedure TW3JsonStructure.SaveToStream(Stream:TStream);
var
mWriter: TWriter;
mtext: String;
begin
SaveToJSON(mText);
mWriter:=TWriter.Create(Stream);
try
mWriter.writeString(mText);
finally
mWriter.free;
end;
end;
procedure TW3JsonStructure.LoadFromStream(Stream:TStream);
var
mText: String;
mReader: TReader;
begin
mReader:=TReader.Create(stream);
try
mText:=mReader.ReadString;
finally
mReader.free;
end;
LoadFromJSON(mtext);
end;
procedure TW3JsonStructure.Clear;
begin
FBuffer:=TVariant.CreateObject;
end;
procedure TW3JsonStructure.Write(name:String;value:variant);
begin
FBuffer[name]:=value;
end;
end.
