JSON properties can be de/serialized verbatim by the default json serializer

This commit is contained in:
Daniele Teti 2019-11-05 14:40:39 +01:00
parent bedc034e6d
commit 23a108896c
14 changed files with 247 additions and 37 deletions

View File

@ -135,6 +135,7 @@ Render(lPerson, False,
- New! `TMVCActiveRecord.DeleteRQL` deletes records using an `RQL` expression as `where` clause.
- New! Microsoft SQLServer Support in `MVCActiveRecord` and RQL (*thanks to one of the biggest Delphi based company in Italy which heavily uses DMVCFramework* and *[DMSContainer](http://www.bittimeprofessionals.it/prodotti/dmscontainer)*)
- New! SQLite support in `MVCActiveRecord` and RQL, so that `MVCActiveRecord` can be used also for Delphi mobile projects!
- Default JSON Serializer can verbatim pass properties with type `JsonDataObjects.TJSONObject` without using `string` as carrier of JSON
- Improved! `ActiveRecordShowCase` sample is much better now.
- Improved! In case of unhandled exception `TMVCEngine` is compliant with the default response content-type (usually it did would reply using `text/plain`).
- **Breaking Change!** In `MVCActiveRecord` attribute `MVCPrimaryKey` has been removed and merged with `MVCTableField`, so now `TMVCActiveRecordFieldOption` is a set of `foPrimaryKey`, `foAutoGenerated`, `foTransient` (check `activerecord_showcase.dproj` sample).

View File

@ -28,7 +28,7 @@ interface
uses
MVCFramework.Serializer.Commons,
Generics.Collections, Vcl.Graphics;
Generics.Collections, Vcl.Graphics, JsonDataObjects;
type
@ -72,6 +72,19 @@ type
property DOB: TDate read GetDOB write SetDOB;
end;
TObjectWithJSONObject = class
private
fJSONObject: TJSONObject;
FStringProp: String;
procedure SetStringProp(const Value: String);
public
constructor Create;
destructor Destroy; override;
property StringProp: String read FStringProp write SetStringProp;
property JSONObject: TJSONObject read fJSONObject;
end;
[MVCNameCase(ncCamelCase)]
TInterfacedPerson = class(TInterfacedObject, IPerson)
private
@ -416,6 +429,25 @@ begin
fName := Value;
end;
{ TObjectWithJSONObject }
constructor TObjectWithJSONObject.Create;
begin
inherited;
fJSONObject := TJsonObject.Create;
end;
destructor TObjectWithJSONObject.Destroy;
begin
fJSONObject.Free;
inherited;
end;
procedure TObjectWithJSONObject.SetStringProp(const Value: String);
begin
FStringProp := Value;
end;
initialization
Randomize;

View File

@ -22,10 +22,8 @@ object MainForm: TMainForm
ActivePage = TabSheet1
Align = alClient
TabOrder = 0
ExplicitWidth = 846
object TabSheet1: TTabSheet
Caption = 'Invoking Plain PODO'
ExplicitWidth = 838
object GroupBox1: TGroupBox
Left = 3
Top = 22
@ -182,18 +180,18 @@ object MainForm: TMainForm
TabOrder = 13
end
object btnDates: TButton
Left = 635
Left = 716
Top = 30
Width = 165
Width = 84
Height = 25
Caption = 'PlayWithDates'
TabOrder = 14
OnClick = btnDatesClick
end
object btnFloatsTests: TButton
Left = 545
Left = 626
Top = 30
Width = 75
Width = 84
Height = 25
Caption = 'Floats'
TabOrder = 15
@ -337,7 +335,6 @@ object MainForm: TMainForm
object TabSheet2: TTabSheet
Caption = 'Invoking DataModule Methods'
ImageIndex = 1
ExplicitWidth = 838
object GroupBox5: TGroupBox
Left = 11
Top = 18
@ -378,6 +375,15 @@ object MainForm: TMainForm
end
end
end
object btnWithJSON: TButton
Left = 552
Top = 76
Width = 75
Height = 25
Caption = 'JSON Prop'
TabOrder = 1
OnClick = btnWithJSONClick
end
object DataSource1: TDataSource
DataSet = FDMemTable1
Left = 767

View File

@ -76,6 +76,7 @@ type
CheckBox1: TCheckBox;
btnDates: TButton;
btnFloatsTests: TButton;
btnWithJSON: TButton;
procedure btnSubstractClick(Sender: TObject);
procedure btnReverseStringClick(Sender: TObject);
procedure edtGetCustomersClick(Sender: TObject);
@ -90,6 +91,7 @@ type
procedure btnSearchClick(Sender: TObject);
procedure btnDatesClick(Sender: TObject);
procedure btnFloatsTestsClick(Sender: TObject);
procedure btnWithJSONClick(Sender: TObject);
private
FExecutor: IMVCJSONRPCExecutor;
FExecutor2: IMVCJSONRPCExecutor;
@ -307,6 +309,25 @@ begin
edtResult.Text := lResp.Result.AsInteger.ToString;
end;
procedure TMainForm.btnWithJSONClick(Sender: TObject);
var
lPerson: TJsonObject;
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
begin
lReq := TJSONRPCRequest.Create;
lReq.Method := 'SaveObjectWithJSON';
lReq.RequestID := 1234;
lPerson := TJsonObject.Create;
lReq.Params.Add(lPerson, pdTJDOJsonObject);
lPerson.S['StringProp'] := 'Hello World';
lPerson.O['JSONObject'] := TJsonObject.Parse('{"name":"Daniele"}') as TJsonObject;
lResp := FExecutor.ExecuteRequest(lReq);
lPerson := lResp.Result.AsObject as TJsonObject;
ShowMessage(lPerson.ToJSON(False));
end;
procedure TMainForm.edtGetCustomersClick(Sender: TObject);
var
lReq: IJSONRPCRequest;

View File

@ -64,19 +64,44 @@ type
function SavePerson(const aPerson: TJsonObject): Integer;
function FloatsTest(const aDouble: Double; const aExtended: Extended): Extended;
procedure DoSomething;
function SaveObjectWithJSON(const WithJSON: TJsonObject): TJsonObject;
// invalid parameters modifiers
procedure InvalidMethod1(var MyVarParam: Integer);
procedure InvalidMethod2(out MyOutParam: Integer);
end;
TUtils = class sealed
class function JSONObjectAs<T: constructor, class>(const JSON: TJsonObject): T;
end;
implementation
uses
System.SysUtils,
MVCFramework.Logger,
System.StrUtils,
System.DateUtils;
System.DateUtils, MVCFramework.Serializer.JsonDataObjects;
class function TUtils.JSONObjectAs<T>(const JSON: TJsonObject): T;
var
lObj: TObject;
lSerializer: TMVCJsonDataObjectsSerializer;
begin
lObj := T.Create;
try
lSerializer := TMVCJsonDataObjectsSerializer.Create;
try
lSerializer.JsonObjectToObject(JSON, lObj, TMVCSerializationType.stProperties, []);
finally
lSerializer.Free;
end;
except
lObj.Free;
raise;
end;
Result := T(lObj);
end;
{ TMyDerivedController }
@ -85,8 +110,7 @@ begin
end;
function TMyObject.FloatsTest(const aDouble: Double;
const aExtended: Extended): Extended;
function TMyObject.FloatsTest(const aDouble: Double; const aExtended: Extended): Extended;
begin
Result := aDouble + aExtended;
end;
@ -224,6 +248,19 @@ begin
Result := Result.ToUpper;
end;
function TMyObject.SaveObjectWithJSON(const WithJSON: TJsonObject): TJsonObject;
var
lObj: TObjectWithJSONObject;
begin
lObj := TUtils.JSONObjectAs<TObjectWithJSONObject>(WithJSON);
try
LogD(lObj);
Result := WithJSON.Clone as TJsonObject;
finally
lObj.Free;
end;
end;
function TMyObject.SavePerson(const aPerson: TJsonObject): Integer;
// var
// lPerson: TPerson;

View File

@ -167,6 +167,11 @@ type
[MVCPath('/simplearray')]
procedure GetSimpleArrays;
[MVCHTTPMethod([httpGET])]
[MVCPath('/objectwithjson')]
procedure GetObjectWithJSONProperty;
end;
implementation
@ -524,6 +529,17 @@ begin
Render<TPerson>(GetPeopleList, False);
end;
procedure TRenderSampleController.GetObjectWithJSONProperty;
var
lObj: TObjectWithJSONObject;
begin
lObj := TObjectWithJSONObject.Create;
lObj.StringProp := 'Daniele Teti';
lObj.JSONObject.S['stringprop'] := 'String Prop';
lObj.JSONObject.O['innerobj'].S['innerstringprop'] := 'Inner String Prop';
Render(lObj);
end;
procedure TRenderSampleController.GetPerson_AsHTML;
begin
ResponseStream.Append('<html><body><ul>').Append('<li>FirstName: Daniele</li>').Append('<li>LastName: Teti')

View File

@ -36,17 +36,17 @@ uses
Web.WebReq,
Web.WebBroker,
MVCFramework.Console,
WebModuleU in 'WebModuleU.pas' {WebModule1: TWebModule} ,
WebModuleU in 'WebModuleU.pas' {WebModule1: TWebModule},
RenderSampleControllerU in 'RenderSampleControllerU.pas',
BusinessObjectsU in '..\commons\BusinessObjectsU.pas',
MyDataModuleU in 'MyDataModuleU.pas' {MyDataModule: TDataModule} ,
MyDataModuleU in 'MyDataModuleU.pas' {MyDataModule: TDataModule},
CustomTypesU in 'CustomTypesU.pas',
CustomTypesSerializersU in 'CustomTypesSerializersU.pas',
InMemoryDataU in 'InMemoryDataU.pas',
MVCFramework.DataSet.Utils in '..\..\sources\MVCFramework.DataSet.Utils.pas',
RandomUtilsU in '..\commons\RandomUtilsU.pas',
MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes
in '..\..\sources\MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes.pas';
MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes in '..\..\sources\MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes.pas',
MVCFramework.Serializer.JsonDataObjects.CustomTypes in '..\..\sources\MVCFramework.Serializer.JsonDataObjects.CustomTypes.pas';
{$R *.res}

View File

@ -148,6 +148,7 @@
<DCCReference Include="..\..\sources\MVCFramework.DataSet.Utils.pas"/>
<DCCReference Include="..\commons\RandomUtilsU.pas"/>
<DCCReference Include="..\..\sources\MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes.pas"/>
<DCCReference Include="..\..\sources\MVCFramework.Serializer.JsonDataObjects.CustomTypes.pas"/>
<None Include="ModelSupport_renders\default.txaPackage"/>
<None Include="ModelSupport_renders\default.txvpck"/>
<None Include="ModelSupport_renders\WebModuleU\default.txaPackage"/>
@ -322,6 +323,12 @@
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="ModelSupport_renders\RenderSampleControllerU\default.txaPackage" Configuration="Release" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="ModelSupport_renders\renders\default.txvpck" Configuration="Release" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
@ -333,37 +340,31 @@
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="ModelSupport_renders\renders\default.txvpck" Configuration="Debug" Class="ProjectFile">
<Platform Name="Linux64">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="ModelSupport_renders\default.txvpck" Configuration="Release" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="ModelSupport_renders\renders1\default.txvpck" Configuration="Release" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="ModelSupport_renders\renders\default.txaPackage" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="bin\renders.exe" Configuration="Release" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>renders.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="ModelSupport_renders\renders\default.txvpck" Configuration="Debug" Class="ProjectFile">
<Platform Name="Linux64">
<DeployFile LocalName="ModelSupport_renders\renders1\default.txvpck" Configuration="Release" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="ModelSupport_renders\RenderSampleControllerU\default.txaPackage" Configuration="Release" Class="ProjectFile">
<DeployFile LocalName="ModelSupport_renders\renders\default.txaPackage" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>

View File

@ -54,8 +54,8 @@ begin
// Max request size in bytes
Config[TMVCConfigKey.MaxRequestSize] := IntToStr(TMVCConstants.DEFAULT_MAX_REQUEST_SIZE);
end);
FMVC.AddController(TMyController);
// To enable compression (deflate, gzip) just add this middleware as the last one
// FMVC.AddController(TMyController);
// To enable compression (deflate, gzip) just add this middleware as the last one
FMVC.AddMiddleware(TMVCCompressionMiddleware.Create);
end;

View File

@ -3,6 +3,10 @@ program swaggeruiwebserver;
{$APPTYPE CONSOLE}
uses
{$IFDEF MSWINDOWS}
WinAPI.ShellAPI,
WinAPI.Windows,
{$ENDIF}
System.SysUtils,
MVCFramework.Logger,
MVCFramework.Commons,

View File

@ -43,8 +43,6 @@ type
TMVCStreamSerializerJsonDataObject = class(TInterfacedObject, IMVCTypeSerializer)
protected
// procedure Serialize(const AElementValue: TValue; var ASerializerObject: TObject;
// const AAttributes: TArray<TCustomAttribute>);
procedure SerializeAttribute(const AElementValue: TValue; const APropertyName: string;
const ASerializerObject: TObject; const AAttributes: TArray<TCustomAttribute>);
procedure SerializeRoot(const AObject: TObject; out ASerializerObject: TObject;

View File

@ -186,7 +186,6 @@ begin
fStringDictionarySerializer := TMVCStringDictionarySerializer.Create;
GetTypeSerializers.Add(TypeInfo(TMVCStringDictionary), TMVCStringDictionarySerializer.Create);
GetTypeSerializers.Add(TypeInfo(TGUID), TMVCGUIDSerializer.Create);
end;
procedure TMVCJsonDataObjectsSerializer.AttributeToJsonDataValue(const AJsonObject: TJDOJsonObject; const AName: string;
@ -315,6 +314,10 @@ begin
ChildJsonArray := AJsonObject.A[AName];
DataSetToJsonArray(TDataSet(ChildObject), ChildJsonArray, TMVCNameCase.ncLowerCase, []);
end
else if ChildObject is TJsonObject then
begin
AJsonObject.O[AName] := TJsonObject(ChildObject).Clone as TJsonObject;
end
else
begin
ChildList := TDuckTypedList.Wrap(ChildObject);
@ -1092,6 +1095,16 @@ var
AttributeValue: TValue;
lKeyName: string;
begin
if AObject is TJsonObject then
begin
if not Assigned(AObject) then
begin
raise EMVCDeserializationException.Create(AObject.ClassName + ' is not assigned');
end;
TJsonObject(AObject).Assign(AJsonObject);
Exit;
end;
ObjType := GetRttiContext.GetType(AObject.ClassType);
case AType of
stDefault, stProperties:

View File

@ -28,7 +28,7 @@ interface
uses
system.TimeSpan, system.SysUtils, generics.collections, system.Classes,
system.Rtti, MVCFramework.Serializer.Commons;
system.Rtti, MVCFramework.Serializer.Commons, JsonDataObjects;
type
TMyObject = class
@ -47,6 +47,7 @@ type
FPropTimeStamp: TTimeStamp;
FPropTime: TTime;
FPropCurrency: Currency;
fPropJSONObject: TJSONObject;
procedure SetPropAnsiString(const Value: AnsiString);
procedure SetPropString(const Value: string);
procedure SetPropInt64(const Value: Int64);
@ -62,6 +63,8 @@ type
procedure SetPropTime(const Value: TTime);
procedure SetPropCurrency(const Value: Currency);
public
constructor Create;
destructor Destroy; override;
function Equals(Obj: TMyObject): boolean; reintroduce;
property PropString: string read FPropString write SetPropString;
property PropAnsiString: AnsiString read FPropAnsiString
@ -79,6 +82,7 @@ type
property PropTimeStamp: TTimeStamp read FPropTimeStamp
write SetPropTimeStamp;
property PropCurrency: Currency read FPropCurrency write SetPropCurrency;
property PropJSONObject: TJSONObject read fPropJSONObject;
end;
TMyChildObject = class
@ -345,6 +349,25 @@ begin
Result.PropTime := EncodeTime(10, 20, 30, 40);
Result.PropDateTime := Result.PropDate + Result.PropTime;
Result.PropTimeStamp := DateTimeToTimeStamp(Result.PropDateTime + 1);
Result.PropJSONObject.S['stringprop1'] := 'This is a string prop';
Result.PropJSONObject.I['intprop1'] := 1234;
Result.PropJSONObject.A['arrprop'].Add(1234);
Result.PropJSONObject.A['arrprop'].Add('Hello World');
Result.PropJSONObject.O['objprop'].S['innerprop1'] := 'value1';
Result.PropJSONObject.O['objprop'].S['innerprop2'] := 'value2';
Result.PropJSONObject.O['objprop'].S['innerprop3'] := 'value3';
end;
constructor TMyObject.Create;
begin
inherited;
fPropJSONObject := TJsonObject.Create;
end;
destructor TMyObject.Destroy;
begin
fPropJSONObject.Free;
inherited;
end;
function TMyObject.Equals(Obj: TMyObject): boolean;
@ -366,6 +389,7 @@ begin
Obj.PropDateTime) = 0);
Result := Result and (Self.PropTimeStamp.Date = Obj.PropTimeStamp.Date) and
(Self.PropTimeStamp.Time = Obj.PropTimeStamp.Time);
Result := Result and (Self.fPropJSONObject.ToJSON() = Obj.PropJSONObject.ToJSON());
end;
procedure TMyObject.SetPropAnsiString(const Value: AnsiString);

View File

@ -58,6 +58,10 @@ type
{ serialize declarations }
[Test]
procedure TestSerializeAllTypes;
[Test]
procedure TestSerializeAllTypesInList;
[Test]
procedure TestSerializeEntity;
[Test]
procedure TestSerializeNil;
@ -160,7 +164,7 @@ implementation
uses
MVCFramework.Serializer.JsonDataObjects.CustomTypes,
MVCFramework.Commons, System.TypInfo;
MVCFramework.Commons, System.TypInfo, BOs;
const
LINE_BREAK = #$A;
@ -661,6 +665,59 @@ begin
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestSerializeAllTypes;
var
lObj1, lObj2: TMyObject;
lSer: string;
begin
lObj1 := GetMyObject;
try
lSer := FSerializer.SerializeObject(lObj1);
lObj2 := TMyObject.Create;
try
FSerializer.DeserializeObject(lSer, lObj2);
Assert.IsTrue(lObj1.Equals(lObj2));
finally
lObj2.Free;
end;
finally
lObj1.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestSerializeAllTypesInList;
var
lList1, lList2: TObjectList<TMyObject>;
lSer: string;
I: Integer;
lObj: TMyObject;
begin
lList1 := TObjectList<TMyObject>.Create;
try
for I := 0 to 9 do
begin
lObj :=GetMyObject;
lObj.PropJSONObject.I['value'] := I;
lList1.Add(lObj);
end;
lSer := FSerializer.SerializeCollection(lList1);
lList2 := TObjectList<TMyObject>.Create;
try
FSerializer.DeserializeCollection(lSer, lList2, TMyObject);
for I := 0 to 9 do
begin
Assert.IsTrue(lList1[I].Equals(lList2[I]));
end;
finally
lList2.Free;
end;
finally
lList1.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestSerializeCollection;
const
JSON =