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)
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! `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 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`.
> 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;
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)]
TPhilosopher = class(TPerson)
private
@ -659,6 +681,22 @@ begin
fPeople := Value;
end;
{ TProgrammerEx }
destructor TProgrammerEx.Destroy;
begin
FMentor.Free;
inherited;
end;
{ TProgrammerEx2 }
destructor TProgrammerEx2.Destroy;
begin
FMentor.Free;
inherited;
end;
initialization
Randomize;

View File

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

View File

@ -92,7 +92,8 @@ type
function TryNullableToJSON(const AValue: TValue; const AJsonObject: TJDOJsonObject; const AName: string): Boolean;
procedure JsonObjectToObject(const AJsonObject: TJDOJsonObject; const AObject: TObject;
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 ACustomAttributes: TArray<TCustomAttribute>);
procedure JsonArrayToList(const AJsonArray: TJDOJsonArray; const AList: IMVCList; const AClazz: TClass;
@ -1098,14 +1099,18 @@ begin
end;
end;
procedure TMVCJsonDataObjectsSerializer.JsonDataValueToAttribute(const AJsonObject: TJDOJsonObject; const AName: string;
var AValue: TValue; const AType: TMVCSerializationType; const AIgnored: TMVCIgnoredList;
const ACustomAttributes: TArray<TCustomAttribute>);
procedure TMVCJsonDataObjectsSerializer.JsonDataValueToAttribute(
const AObject: TObject;
const ARttiMember: TRttiMember;
const AJsonObject: TJDOJsonObject;
const AName: string; var AValue: TValue; const AType: TMVCSerializationType;
const AIgnored: TMVCIgnoredList; const ACustomAttributes: TArray<TCustomAttribute>);
var
ChildObject: TObject;
ChildList: IMVCList;
ChildListOfAtt: MVCListOfAttribute;
LEnumAsAttr: MVCEnumSerializationAttribute;
lOwnedAttribute: MVCOwnedAttribute;
LEnumMappedValues: TList<string>;
LEnumSerType: TMVCEnumSerializationType;
LClazz: TClass;
@ -1113,17 +1118,85 @@ var
lOutInteger: Integer;
lOutInteger64: Int64;
lTypeInfo: PTypeInfo;
lJSONExists: Boolean;
lJSONIsNull: Boolean;
lChildObjectAssigned: Boolean;
begin
ChildObject := nil;
lTypeInfo := AValue.TypeInfo;
if AValue.Kind in [tkClass, tkInterface] then
begin
ChildObject := nil;
if not AValue.IsEmpty and (AValue.Kind = tkInterface) then
ChildObject := TObject(AValue.AsInterface)
else if AValue.Kind = tkClass then
ChildObject := AValue.AsObject;
if not AValue.IsEmpty then
begin
if AValue.Kind = tkInterface then
ChildObject := TObject(AValue.AsInterface)
else
ChildObject := AValue.AsObject;
end;
if Assigned(ChildObject) then
lTypeInfo := ChildObject.ClassInfo;
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;
case AType of
stUnknown, stDefault, stProperties:
TRttiProperty(ARttiMember).SetValue(AObject, ChildObject);
stFields:
TRttiField(ARttiMember).SetValue(AObject, ChildObject);
end;
end; //end cases
end;
end;
if GetTypeSerializers.ContainsKey(lTypeInfo) then
@ -1325,13 +1398,13 @@ begin
case AValue.Kind of
tkInterface:
begin
ChildObject := TObject(AValue.AsInterface);
//ChildObject := TObject(AValue.AsInterface);
JsonObjectToObject(AJsonObject.O[AName], ChildObject, GetSerializationType(ChildObject, AType),
AIgnored);
end;
tkClass:
begin
ChildObject := AValue.AsObject;
//ChildObject := AValue.AsObject;
JsonObjectToObject(AJsonObject.O[AName], ChildObject, GetSerializationType(ChildObject, AType),
AIgnored);
end;
@ -1511,8 +1584,9 @@ begin
end;
end;
procedure TMVCJsonDataObjectsSerializer.JsonObjectToObject(const AJsonObject: TJDOJsonObject; const AObject: TObject;
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList);
procedure TMVCJsonDataObjectsSerializer.JsonObjectToObject(const AJsonObject: TJDOJsonObject;
const AObject: TObject; const AType: TMVCSerializationType;
const AIgnoredAttributes: TMVCIgnoredList);
var
lObjType: TRttiType;
lProp: TRttiProperty;
@ -1558,7 +1632,9 @@ begin
begin
lAttributeValue := lProp.GetValue(AObject);
lKeyName := TMVCSerializerHelper.GetKeyName(lProp, lObjType);
JsonDataValueToAttribute(AJsonObject, lKeyName, lAttributeValue, AType, AIgnoredAttributes,
JsonDataValueToAttribute(
AObject, lProp,
AJsonObject, lKeyName, lAttributeValue, AType, AIgnoredAttributes,
lProp.GetAttributes);
if (not lAttributeValue.IsEmpty) and (not lAttributeValue.IsObject) and lProp.IsWritable then
begin
@ -1592,9 +1668,12 @@ begin
begin
lAttributeValue := lFld.GetValue(AObject);
lKeyName := TMVCSerializerHelper.GetKeyName(lFld, lObjType);
JsonDataValueToAttribute(AJsonObject, lKeyName, lAttributeValue, AType, AIgnoredAttributes,
lFld.GetAttributes);
if not lAttributeValue.IsEmpty then
JsonDataValueToAttribute(
AObject, lFld,
AJsonObject, lKeyName,
lAttributeValue, AType,
AIgnoredAttributes, lFld.GetAttributes);
if (not lAttributeValue.IsEmpty) and (not lAttributeValue.IsObject) then
lFld.SetValue(AObject, lAttributeValue);
end;
except

View File

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

View File

@ -58,7 +58,6 @@ type
{ serialize declarations }
[Test]
// [Category('this')]
procedure TestSerializeAllTypes;
[Test]
procedure TestSerializeDateTimeProperty;
@ -73,7 +72,6 @@ type
[Test]
procedure TestSerializeEntityUpperCaseNames;
[Test]
// [Category('this')]
procedure TestSerializeEntityWithArray;
[Test]
procedure TestSerializeEntityLowerCaseNames;
@ -139,6 +137,38 @@ type
[Test]
[Category('serializers')]
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;
TMVCEntityCustomSerializerJsonDataObjects = class(TInterfacedObject, IMVCTypeSerializer)
@ -578,6 +608,244 @@ begin
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;
var
lObj: TPartialSerializableType;

View File

@ -308,6 +308,14 @@ type
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 }
[MVCHTTPMethod([httpGET])]
[MVCPath('/website/list')]
@ -362,6 +370,18 @@ uses
{ 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;
begin
case Context.Request.HTTPMethod of