New! Added the new MVCOwned attribute which allows to auto-create nested objects in the deserialization phase. This will not change the current behavior, you ned to explocitly define a property (or a field) as MVCOwned to allows the serialization to create or destroy object for you.

This commit is contained in:
Daniele Teti 2021-08-17 15:10:58 +02:00
parent 6837182cc3
commit 4986d9ba3f
7 changed files with 450 additions and 21 deletions

View File

@ -423,6 +423,10 @@ This version introduced new features in many different areas (swagger, server si
## Next Release: 3.2.2-nitrogen ("repo" version) ## Next Release: 3.2.2-nitrogen ("repo" version)
The current beta release is named 3.2.2-nitrogen. If you want to stay on the-edge or just help the testers, clone the repo and start using it. Be warned: it may contains unstable code.
### Whet's new in 3.2.2-nitrogen (currently in beta)
- ⚡New `TMVCRESTClient` implementation based on *Net components, the previous one was based on INDY Components (thanks to [João Antônio Duarte](https://github.com/joaoduarte19)). - ⚡New `TMVCRESTClient` implementation based on *Net components, the previous one was based on INDY Components (thanks to [João Antônio Duarte](https://github.com/joaoduarte19)).
- ⚡New! `MVCJSONRPCAllowGET` attribute allows a remote JSON-RPC published object, or a specific method, to be called using GET HTTP Verb as well as POST HTTP Verb. POST is always available, GET is available only if explicitly allowed. `IMVCJSONRPCExecutor` allows to specify which HTTP Verb to use when call the server JSON.RPC methods. The default verb can be injected in the constructor and each `ExecuteRequest`/`ExecuteNotification` allows to override od adhere to the instance default. - ⚡New! `MVCJSONRPCAllowGET` attribute allows a remote JSON-RPC published object, or a specific method, to be called using GET HTTP Verb as well as POST HTTP Verb. POST is always available, GET is available only if explicitly allowed. `IMVCJSONRPCExecutor` allows to specify which HTTP Verb to use when call the server JSON.RPC methods. The default verb can be injected in the constructor and each `ExecuteRequest`/`ExecuteNotification` allows to override od adhere to the instance default.
@ -451,6 +455,8 @@ This version introduced new features in many different areas (swagger, server si
- ⚡New! Added new default parameter to `TMVCActiveRecord.RemoveDefaultConnection` and `TMVCActiveRecord.RemoveConnection` to avoid exceptions in case of not initialized connection. - ⚡New! Added new default parameter to `TMVCActiveRecord.RemoveDefaultConnection` and `TMVCActiveRecord.RemoveConnection` to avoid exceptions in case of not initialized connection.
- ⚡New! Added the new `MVCOwned` attribute which allows to auto-create nested objects in the deserialization phase. This will not change the current behavior, you ned to explocitly define a property (or a field) as `MVCOwned` to allows the serialization to create or destroy object for you.
- ⚡New! Added `TMVCJWTBlackListMiddleware` to allow black-listing and (a sort of) logout for a JWT based authentication. This middleware **must** be registered **after** the `TMVCJWTAuthenticationMiddleware`. - ⚡New! Added `TMVCJWTBlackListMiddleware` to allow black-listing and (a sort of) logout for a JWT based authentication. This middleware **must** be registered **after** the `TMVCJWTAuthenticationMiddleware`.
> This middleware provides 2 events named: `OnAcceptToken` (invoked when a request contains a token - need to returns true/false if the token is still accepted by the server or not) and `OnNewJWTToBlackList` (invoked when a client ask to blacklist its current token). There is a new sample available which shows the funtionalities: `samples\middleware_jwtblacklist`. > This middleware provides 2 events named: `OnAcceptToken` (invoked when a request contains a token - need to returns true/false if the token is still accepted by the server or not) and `OnNewJWTToBlackList` (invoked when a client ask to blacklist its current token). There is a new sample available which shows the funtionalities: `samples\middleware_jwtblacklist`.

View File

@ -268,6 +268,28 @@ type
property Skills: string read FSkills write SetSkills; property Skills: string read FSkills write SetSkills;
end; end;
[MVCNameCase(ncLowerCase)]
TProgrammerEx = class(TProgrammer)
private
[MVCOwned] //required only for field serialization
FMentor: TProgrammerEx;
public
destructor Destroy; override;
[MVCOwned] //required only for property serialization
property Mentor: TProgrammerEx read FMentor write fMentor;
end;
[MVCNameCase(ncLowerCase)]
TProgrammerEx2 = class(TProgrammer)
private
FMentor: TProgrammer;
public
destructor Destroy; override;
[MVCOwned(TProgrammerEx2)]
property Mentor: TProgrammer read FMentor write fMentor;
end;
[MVCNameCase(ncLowerCase)] [MVCNameCase(ncLowerCase)]
TPhilosopher = class(TPerson) TPhilosopher = class(TPerson)
private private
@ -659,6 +681,22 @@ begin
fPeople := Value; fPeople := Value;
end; end;
{ TProgrammerEx }
destructor TProgrammerEx.Destroy;
begin
FMentor.Free;
inherited;
end;
{ TProgrammerEx2 }
destructor TProgrammerEx2.Destroy;
begin
FMentor.Free;
inherited;
end;
initialization initialization
Randomize; Randomize;

View File

@ -185,6 +185,15 @@ type
property MappedValues: TList<string> read FMappedValues; property MappedValues: TList<string> read FMappedValues;
end; end;
MVCOwnedAttribute = class(TCustomAttribute)
private
fClassRef: TClass;
public
constructor Create(const ClassRef: TClass = nil);
property ClassRef: TClass read fClassRef;
end;
TMVCSerializerHelper = record TMVCSerializerHelper = record
private private
{ private declarations } { private declarations }
@ -214,7 +223,7 @@ type
class function StringToTypeKind(const AValue: string): TTypeKind; static; class function StringToTypeKind(const AValue: string): TTypeKind; static;
class function CreateObject(const AObjectType: TRttiType): TObject; overload; static; class function CreateObject(const AObjectType: TRttiType): TObject; overload; static;
class function CreateObject(const AQualifiedClassName: string): TObject; overload; static; class function CreateObject(const AQualifiedClassName: string): TObject; overload; static;
class function IsAPropertyToSkip(const aPropName: string): Boolean; static; class function IsAPropertyToSkip(const aPropName: string): Boolean; static; inline;
end; end;
TMVCLinksCallback = reference to procedure(const Links: TMVCStringDictionary); TMVCLinksCallback = reference to procedure(const Links: TMVCStringDictionary);
@ -1731,6 +1740,14 @@ begin
inherited; inherited;
end; end;
{ MVCOwnedAttribute }
constructor MVCOwnedAttribute.Create(const ClassRef: TClass);
begin
inherited Create;
fClassRef := ClassRef;
end;
initialization initialization
gLocalTimeStampAsUTC := False; gLocalTimeStampAsUTC := False;

View File

@ -92,7 +92,8 @@ type
function TryNullableToJSON(const AValue: TValue; const AJsonObject: TJDOJsonObject; const AName: string): Boolean; function TryNullableToJSON(const AValue: TValue; const AJsonObject: TJDOJsonObject; const AName: string): Boolean;
procedure JsonObjectToObject(const AJsonObject: TJDOJsonObject; const AObject: TObject; procedure JsonObjectToObject(const AJsonObject: TJDOJsonObject; const AObject: TObject;
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList); const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList);
procedure JsonDataValueToAttribute(const AJsonObject: TJDOJsonObject; const AName: string; var AValue: TValue; procedure JsonDataValueToAttribute(const AObject: TObject; const ARttiMember: TRttiMember;
const AJsonObject: TJDOJsonObject; const AName: string; var AValue: TValue;
const AType: TMVCSerializationType; const AIgnored: TMVCIgnoredList; const AType: TMVCSerializationType; const AIgnored: TMVCIgnoredList;
const ACustomAttributes: TArray<TCustomAttribute>); const ACustomAttributes: TArray<TCustomAttribute>);
procedure JsonArrayToList(const AJsonArray: TJDOJsonArray; const AList: IMVCList; const AClazz: TClass; procedure JsonArrayToList(const AJsonArray: TJDOJsonArray; const AList: IMVCList; const AClazz: TClass;
@ -1098,14 +1099,18 @@ begin
end; end;
end; end;
procedure TMVCJsonDataObjectsSerializer.JsonDataValueToAttribute(const AJsonObject: TJDOJsonObject; const AName: string; procedure TMVCJsonDataObjectsSerializer.JsonDataValueToAttribute(
var AValue: TValue; const AType: TMVCSerializationType; const AIgnored: TMVCIgnoredList; const AObject: TObject;
const ACustomAttributes: TArray<TCustomAttribute>); const ARttiMember: TRttiMember;
const AJsonObject: TJDOJsonObject;
const AName: string; var AValue: TValue; const AType: TMVCSerializationType;
const AIgnored: TMVCIgnoredList; const ACustomAttributes: TArray<TCustomAttribute>);
var var
ChildObject: TObject; ChildObject: TObject;
ChildList: IMVCList; ChildList: IMVCList;
ChildListOfAtt: MVCListOfAttribute; ChildListOfAtt: MVCListOfAttribute;
LEnumAsAttr: MVCEnumSerializationAttribute; LEnumAsAttr: MVCEnumSerializationAttribute;
lOwnedAttribute: MVCOwnedAttribute;
LEnumMappedValues: TList<string>; LEnumMappedValues: TList<string>;
LEnumSerType: TMVCEnumSerializationType; LEnumSerType: TMVCEnumSerializationType;
LClazz: TClass; LClazz: TClass;
@ -1113,17 +1118,85 @@ var
lOutInteger: Integer; lOutInteger: Integer;
lOutInteger64: Int64; lOutInteger64: Int64;
lTypeInfo: PTypeInfo; lTypeInfo: PTypeInfo;
lJSONExists: Boolean;
lJSONIsNull: Boolean;
lChildObjectAssigned: Boolean;
begin begin
ChildObject := nil;
lTypeInfo := AValue.TypeInfo; lTypeInfo := AValue.TypeInfo;
if AValue.Kind in [tkClass, tkInterface] then if AValue.Kind in [tkClass, tkInterface] then
begin begin
ChildObject := nil; if not AValue.IsEmpty then
if not AValue.IsEmpty and (AValue.Kind = tkInterface) then begin
if AValue.Kind = tkInterface then
ChildObject := TObject(AValue.AsInterface) ChildObject := TObject(AValue.AsInterface)
else if AValue.Kind = tkClass then else
ChildObject := AValue.AsObject; ChildObject := AValue.AsObject;
end;
if Assigned(ChildObject) then if Assigned(ChildObject) then
begin
lTypeInfo := ChildObject.ClassInfo
end;
if TMVCSerializerHelper.AttributeExists<MVCOwnedAttribute>(ACustomAttributes, lOwnedAttribute) then
begin
{ Now, can happens the following situations:
ChildObject JSON Outcome
----------- --------- ----------------------------------------------------
1) Created Exists The JSON is loaded in the object (default)
2) Created NotExists Leave unchanged
3) Created is Null If ChildObject is Owned must be destroyed
4) nil Exists If ChildObject is Owned, create it and load the json
5) nil NotExists Leave unchanged
6) nil is Null Leave unchanged
--> So, we'll manage only case 3 and 4 <--
}
lJSONExists := AJsonObject.Contains(AName);
lJSONIsNull := lJSONExists and AJsonObject.IsNull(AName);
lChildObjectAssigned := ChildObject <> nil;
//case 3
if lChildObjectAssigned and lJSONIsNull then
begin
ChildObject.Free;
case AType of
stUnknown, stDefault, stProperties:
TRttiProperty(ARttiMember).SetValue(AObject, nil);
stFields:
TRttiField(ARttiMember).SetValue(AObject, nil);
end;
end
//case 4
else if (not lChildObjectAssigned) and lJSONExists and (not lJSONIsNull) then
begin
if lOwnedAttribute.ClassRef <> nil then
begin
ChildObject := TMVCSerializerHelper.CreateObject(lOwnedAttribute.ClassRef.QualifiedClassName);
end
else
begin
case AType of
stUnknown, stDefault, stProperties:
ChildObject := TMVCSerializerHelper.CreateObject(TRttiProperty(ARttiMember).PropertyType);
stFields:
ChildObject := TMVCSerializerHelper.CreateObject(TRttiField(ARttiMember).FieldType);
end;
end;
lTypeInfo := ChildObject.ClassInfo; lTypeInfo := ChildObject.ClassInfo;
case AType of
stUnknown, stDefault, stProperties:
TRttiProperty(ARttiMember).SetValue(AObject, ChildObject);
stFields:
TRttiField(ARttiMember).SetValue(AObject, ChildObject);
end;
end; //end cases
end;
end; end;
if GetTypeSerializers.ContainsKey(lTypeInfo) then if GetTypeSerializers.ContainsKey(lTypeInfo) then
@ -1325,13 +1398,13 @@ begin
case AValue.Kind of case AValue.Kind of
tkInterface: tkInterface:
begin begin
ChildObject := TObject(AValue.AsInterface); //ChildObject := TObject(AValue.AsInterface);
JsonObjectToObject(AJsonObject.O[AName], ChildObject, GetSerializationType(ChildObject, AType), JsonObjectToObject(AJsonObject.O[AName], ChildObject, GetSerializationType(ChildObject, AType),
AIgnored); AIgnored);
end; end;
tkClass: tkClass:
begin begin
ChildObject := AValue.AsObject; //ChildObject := AValue.AsObject;
JsonObjectToObject(AJsonObject.O[AName], ChildObject, GetSerializationType(ChildObject, AType), JsonObjectToObject(AJsonObject.O[AName], ChildObject, GetSerializationType(ChildObject, AType),
AIgnored); AIgnored);
end; end;
@ -1511,8 +1584,9 @@ begin
end; end;
end; end;
procedure TMVCJsonDataObjectsSerializer.JsonObjectToObject(const AJsonObject: TJDOJsonObject; const AObject: TObject; procedure TMVCJsonDataObjectsSerializer.JsonObjectToObject(const AJsonObject: TJDOJsonObject;
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList); const AObject: TObject; const AType: TMVCSerializationType;
const AIgnoredAttributes: TMVCIgnoredList);
var var
lObjType: TRttiType; lObjType: TRttiType;
lProp: TRttiProperty; lProp: TRttiProperty;
@ -1558,7 +1632,9 @@ begin
begin begin
lAttributeValue := lProp.GetValue(AObject); lAttributeValue := lProp.GetValue(AObject);
lKeyName := TMVCSerializerHelper.GetKeyName(lProp, lObjType); lKeyName := TMVCSerializerHelper.GetKeyName(lProp, lObjType);
JsonDataValueToAttribute(AJsonObject, lKeyName, lAttributeValue, AType, AIgnoredAttributes, JsonDataValueToAttribute(
AObject, lProp,
AJsonObject, lKeyName, lAttributeValue, AType, AIgnoredAttributes,
lProp.GetAttributes); lProp.GetAttributes);
if (not lAttributeValue.IsEmpty) and (not lAttributeValue.IsObject) and lProp.IsWritable then if (not lAttributeValue.IsEmpty) and (not lAttributeValue.IsObject) and lProp.IsWritable then
begin begin
@ -1592,9 +1668,12 @@ begin
begin begin
lAttributeValue := lFld.GetValue(AObject); lAttributeValue := lFld.GetValue(AObject);
lKeyName := TMVCSerializerHelper.GetKeyName(lFld, lObjType); lKeyName := TMVCSerializerHelper.GetKeyName(lFld, lObjType);
JsonDataValueToAttribute(AJsonObject, lKeyName, lAttributeValue, AType, AIgnoredAttributes, JsonDataValueToAttribute(
lFld.GetAttributes); AObject, lFld,
if not lAttributeValue.IsEmpty then AJsonObject, lKeyName,
lAttributeValue, AType,
AIgnoredAttributes, lFld.GetAttributes);
if (not lAttributeValue.IsEmpty) and (not lAttributeValue.IsObject) then
lFld.SetValue(AObject, lAttributeValue); lFld.SetValue(AObject, lAttributeValue);
end; end;
except except

View File

@ -219,6 +219,7 @@
</DCCReference> </DCCReference>
<DCCReference Include="..\..\..\sources\MVCFramework.Commons.pas"/> <DCCReference Include="..\..\..\sources\MVCFramework.Commons.pas"/>
<DCCReference Include="..\..\..\sources\MVCFramework.Serializer.JsonDataObjects.CustomTypes.pas"/> <DCCReference Include="..\..\..\sources\MVCFramework.Serializer.JsonDataObjects.CustomTypes.pas"/>
<DCCReference Include="..\..\..\sources\MVCFramework.SQLGenerators.Firebird.pas"/>
<BuildConfiguration Include="Base"> <BuildConfiguration Include="Base">
<Key>Base</Key> <Key>Base</Key>
</BuildConfiguration> </BuildConfiguration>

View File

@ -58,7 +58,6 @@ type
{ serialize declarations } { serialize declarations }
[Test] [Test]
// [Category('this')]
procedure TestSerializeAllTypes; procedure TestSerializeAllTypes;
[Test] [Test]
procedure TestSerializeDateTimeProperty; procedure TestSerializeDateTimeProperty;
@ -73,7 +72,6 @@ type
[Test] [Test]
procedure TestSerializeEntityUpperCaseNames; procedure TestSerializeEntityUpperCaseNames;
[Test] [Test]
// [Category('this')]
procedure TestSerializeEntityWithArray; procedure TestSerializeEntityWithArray;
[Test] [Test]
procedure TestSerializeEntityLowerCaseNames; procedure TestSerializeEntityLowerCaseNames;
@ -139,6 +137,38 @@ type
[Test] [Test]
[Category('serializers')] [Category('serializers')]
procedure TestSerializeListWithNulls2; procedure TestSerializeListWithNulls2;
[Test]
[Category('serializers')]
procedure TestDeserializeOwnedProperty_WithPropertyUnassigned_JSONExists;
[Test]
[Category('serializers')]
procedure TestDeserializeOwnedProperty_WithPropertyAssigned_JSONExists;
[Test]
[Category('serializers')]
procedure TestDeserializeOwnedProperty_WithPropertyAssigned_JSONNull;
[Test]
[Category('serializers')]
procedure TestDeserializeOwnedProperty_WithPropertyAssigned_JSONNotExists;
[Test]
[Category('serializers')]
procedure TestDeserializeOwnedProperty_WithPropertyUnAssigned_JSONNull;
[Test]
[Category('serializers')]
procedure TestDeserializeOwnedProperty_WithFieldsUnassigned_JSONExists;
[Test]
[Category('serializers')]
procedure TestDeserializeOwnedField_WithFieldsAssigned_JSONNull;
[Test]
[Category('serializers')]
procedure TestDeserializeOwnedProperty_WithPropertyUnassigned_JSONExists_Polimorphic;
end; end;
TMVCEntityCustomSerializerJsonDataObjects = class(TInterfacedObject, IMVCTypeSerializer) TMVCEntityCustomSerializerJsonDataObjects = class(TInterfacedObject, IMVCTypeSerializer)
@ -578,6 +608,244 @@ begin
end; end;
end; end;
procedure TMVCTestSerializerJsonDataObjects.TestDeserializeOwnedProperty_WithPropertyUnassigned_JSONExists;
const
lJSON = '{' +
' "skills": "",' +
' "id": 2,' +
' "firstname": "child firstname",' +
' "lastname": "child lastname",' +
' "dob": null,' +
' "married": false,' +
' "mentor": { ' +
' "mentor": null, ' +
' "skills": "superb programmer", ' +
' "firstname": "mentor firstname", ' +
' "lastname": "mentor lasttname", ' +
' "dob": null, ' +
' "married": false, ' +
' "id": 2 ' +
' }' +
' }';
var
lProgrammerEx: TProgrammerEx;
begin
lProgrammerEx := TProgrammerEx.Create;
try
fSerializer.DeserializeObject(lJSON, lProgrammerEx);
Assert.AreEqual('child firstname', lProgrammerEx.FirstName);
Assert.IsNotNull(lProgrammerEx.Mentor);
Assert.IsNull(lProgrammerEx.Mentor.Mentor);
Assert.AreEqual('mentor firstname', lProgrammerEx.Mentor.FirstName);
finally
lProgrammerEx.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestDeserializeOwnedProperty_WithPropertyUnassigned_JSONExists_Polimorphic;
const
lJSON = '{' +
' "skills": "",' +
' "id": 2,' +
' "firstname": "child firstname",' +
' "lastname": "child lastname",' +
' "dob": null,' +
' "married": false,' +
' "mentor": { ' +
' "mentor": null, ' +
' "skills": "superb programmer", ' +
' "firstname": "mentor firstname", ' +
' "lastname": "mentor lasttname", ' +
' "dob": null, ' +
' "married": false, ' +
' "id": 2 ' +
' }' +
' }';
var
lProgrammerEx: TProgrammerEx2;
begin
lProgrammerEx := TProgrammerEx2.Create;
try
fSerializer.DeserializeObject(lJSON, lProgrammerEx);
Assert.AreEqual('child firstname', lProgrammerEx.FirstName);
Assert.IsNotNull(lProgrammerEx.Mentor);
Assert.IsTrue(lProgrammerEx.Mentor is TProgrammerEx2, lProgrammerEx.Mentor.ClassName);
Assert.AreEqual('mentor firstname', lProgrammerEx.Mentor.FirstName);
finally
lProgrammerEx.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestDeserializeOwnedProperty_WithPropertyUnAssigned_JSONNull;
const
lJSON = '{' +
' "skills": "",' +
' "id": 2,' +
' "firstname": "child firstname",' +
' "lastname": "child lastname",' +
' "dob": null,' +
' "married": false,' +
' "mentor": null ' +
' }';
var
lProgrammerEx: TProgrammerEx;
begin
lProgrammerEx := TProgrammerEx.Create;
try
fSerializer.DeserializeObject(lJSON, lProgrammerEx);
Assert.AreEqual('child firstname', lProgrammerEx.FirstName);
Assert.IsNull(lProgrammerEx.Mentor);
finally
lProgrammerEx.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestDeserializeOwnedField_WithFieldsAssigned_JSONNull;
const
lJSON = '{' +
' "fskills": "",' +
' "fid": 2,' +
' "ffirstname": "child firstname",' +
' "flastname": "child lastname",' +
' "fdob": null,' +
' "fmarried": false,' +
' "fmentor": null ' +
' }';
var
lProgrammerEx: TProgrammerEx;
begin
lProgrammerEx := TProgrammerEx.Create;
try
lProgrammerEx.Mentor := TProgrammerEx.Create;
fSerializer.DeserializeObject(lJSON, lProgrammerEx, stFields);
Assert.AreEqual('child firstname', lProgrammerEx.FirstName);
Assert.IsNull(lProgrammerEx.Mentor);
finally
lProgrammerEx.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestDeserializeOwnedProperty_WithFieldsUnassigned_JSONExists;
const
lJSON = '{' +
' "fskills": "",' +
' "fid": 2,' +
' "ffirstname": "child firstname",' +
' "flastname": "child lastname",' +
' "fdob": null,' +
' "fmarried": false,' +
' "fmentor": { ' +
' "fmentor": null, ' +
' "fskills": "superb programmer", ' +
' "ffirstname": "mentor firstname", ' +
' "flastname": "mentor lasttname", ' +
' "fdob": null, ' +
' "fmarried": false, ' +
' "fid": 2 ' +
' }' +
' }';
var
lProgrammerEx: TProgrammerEx;
begin
lProgrammerEx := TProgrammerEx.Create;
try
fSerializer.DeserializeObject(lJSON, lProgrammerEx, stFields);
Assert.AreEqual('child firstname', lProgrammerEx.FirstName);
Assert.IsNotNull(lProgrammerEx.Mentor);
Assert.IsNull(lProgrammerEx.Mentor.Mentor);
Assert.AreEqual('mentor firstname', lProgrammerEx.Mentor.FirstName);
finally
lProgrammerEx.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestDeserializeOwnedProperty_WithPropertyAssigned_JSONExists;
const
lJSON = '{' +
' "skills": "",' +
' "id": 2,' +
' "firstname": "child firstname",' +
' "lastname": "child lastname",' +
' "dob": null,' +
' "married": false,' +
' "mentor": { ' +
' "mentor": null, ' +
' "skills": "superb programmer", ' +
' "firstname": "mentor firstname", ' +
' "lastname": "mentor lasttname", ' +
' "dob": null, ' +
' "married": false, ' +
' "id": 2 ' +
' }' +
' }';
var
lProgrammerEx: TProgrammerEx;
begin
lProgrammerEx := TProgrammerEx.Create;
try
lProgrammerEx.Mentor := TProgrammerEx.Create;
fSerializer.DeserializeObject(lJSON, lProgrammerEx);
Assert.AreEqual('child firstname', lProgrammerEx.FirstName);
Assert.IsNotNull(lProgrammerEx.Mentor);
Assert.IsNull(lProgrammerEx.Mentor.Mentor);
Assert.AreEqual('mentor firstname', lProgrammerEx.Mentor.FirstName);
finally
lProgrammerEx.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestDeserializeOwnedProperty_WithPropertyAssigned_JSONNotExists;
const
lJSON = '{' +
' "skills": "",' +
' "id": 2,' +
' "firstname": "child firstname",' +
' "lastname": "child lastname",' +
' "dob": null,' +
' "married": false' +
' }';
var
lProgrammerEx: TProgrammerEx;
begin
lProgrammerEx := TProgrammerEx.Create;
try
lProgrammerEx.Mentor := TProgrammerEx.Create;
lProgrammerEx.Mentor.FirstName := 'existent_value';
fSerializer.DeserializeObject(lJSON, lProgrammerEx);
Assert.AreEqual('child firstname', lProgrammerEx.FirstName);
Assert.IsNotNull(lProgrammerEx.Mentor);
Assert.AreEqual('existent_value', lProgrammerEx.Mentor.FirstName);
finally
lProgrammerEx.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestDeserializeOwnedProperty_WithPropertyAssigned_JSONNull;
const
lJSON = '{' +
' "skills": "",' +
' "id": 2,' +
' "firstname": "child firstname",' +
' "lastname": "child lastname",' +
' "dob": null,' +
' "married": false,' +
' "mentor": null ' +
' }';
var
lProgrammerEx: TProgrammerEx;
begin
lProgrammerEx := TProgrammerEx.Create;
try
lProgrammerEx.Mentor := TProgrammerEx.Create;
fSerializer.DeserializeObject(lJSON, lProgrammerEx);
Assert.AreEqual('child firstname', lProgrammerEx.FirstName);
Assert.IsNull(lProgrammerEx.Mentor);
finally
lProgrammerEx.Free;
end;
end;
procedure TMVCTestSerializerJsonDataObjects.TestDoNotSerializeDoNotDeSerialize; procedure TMVCTestSerializerJsonDataObjects.TestDoNotSerializeDoNotDeSerialize;
var var
lObj: TPartialSerializableType; lObj: TPartialSerializableType;

View File

@ -308,6 +308,14 @@ type
procedure PostInject50(const [MVCFromBody] People: TObjectList<TPerson>); procedure PostInject50(const [MVCFromBody] People: TObjectList<TPerson>);
[MVCHTTPMethod([httpPOST])]
[MVCPath('/programmerex')]
procedure CreateProgrammerEx(const [MVCFromBody] ProgrammerEx: TProgrammerEx);
[MVCHTTPMethod([httpPOST])]
[MVCPath('/programmerex2')]
procedure CreateProgrammerEx2(const [MVCFromBody] ProgrammerEx2: TProgrammerEx2);
{ templates } { templates }
[MVCHTTPMethod([httpGET])] [MVCHTTPMethod([httpGET])]
[MVCPath('/website/list')] [MVCPath('/website/list')]
@ -362,6 +370,18 @@ uses
{ TTestServerController } { TTestServerController }
procedure TTestServerController.CreateProgrammerEx(
const ProgrammerEx: TProgrammerEx);
begin
Render(ProgrammerEx, False);
end;
procedure TTestServerController.CreateProgrammerEx2(
const ProgrammerEx2: TProgrammerEx2);
begin
Render(ProgrammerEx2, False);
end;
procedure TTestServerController.DataSetHandling; procedure TTestServerController.DataSetHandling;
begin begin
case Context.Request.HTTPMethod of case Context.Request.HTTPMethod of