2017-03-28 14:52:13 +02:00
|
|
|
|
// ***************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Delphi MVC Framework
|
|
|
|
|
//
|
2024-01-02 17:04:27 +01:00
|
|
|
|
// Copyright (c) 2010-2024 Daniele Teti and the DMVCFramework Team
|
2017-03-28 14:52:13 +02:00
|
|
|
|
//
|
|
|
|
|
// https://github.com/danieleteti/delphimvcframework
|
|
|
|
|
//
|
2024-01-02 17:04:27 +01:00
|
|
|
|
// Collaborators with this file: Ezequiel Juliano M<>ller (ezequieljuliano@gmail.com)
|
2017-03-28 14:52:13 +02:00
|
|
|
|
//
|
|
|
|
|
// ***************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
//
|
|
|
|
|
// ***************************************************************************
|
|
|
|
|
|
|
|
|
|
unit MVCFramework.Serializer.Abstract;
|
|
|
|
|
|
|
|
|
|
{$I dmvcframework.inc}
|
|
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
|
|
uses
|
|
|
|
|
System.Rtti,
|
|
|
|
|
System.TypInfo,
|
2017-03-29 14:49:35 +02:00
|
|
|
|
System.Classes,
|
2017-03-28 14:52:13 +02:00
|
|
|
|
System.Generics.Collections,
|
|
|
|
|
MVCFramework.Serializer.Intf,
|
2020-04-20 17:56:17 +02:00
|
|
|
|
MVCFramework.Serializer.Commons, MVCFramework.Commons;
|
2017-03-28 14:52:13 +02:00
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
|
2017-07-16 19:36:44 +02:00
|
|
|
|
TMVCAbstractSerializer = class Abstract(TInterfacedObject)
|
2017-03-28 14:52:13 +02:00
|
|
|
|
private
|
|
|
|
|
FRttiContext: TRttiContext;
|
|
|
|
|
FTypeSerializers: TDictionary<PTypeInfo, IMVCTypeSerializer>;
|
|
|
|
|
protected
|
2020-09-16 15:56:14 +02:00
|
|
|
|
FConfig: TMVCConfig;
|
2017-03-28 14:52:13 +02:00
|
|
|
|
function GetRttiContext: TRttiContext;
|
|
|
|
|
function GetSerializationType(const AObject: TObject; const ADefaultValue: TMVCSerializationType = stDefault): TMVCSerializationType;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
function GetDataType(const AOwner: TComponent; const AComponentName: string; const ADefaultValue: TMVCDataType): TMVCDataType;
|
2017-03-28 14:52:13 +02:00
|
|
|
|
public
|
2022-08-12 10:50:46 +02:00
|
|
|
|
class function IsIgnoredAttribute(const AAttributes: TMVCIgnoredList; const AName: string): Boolean;
|
|
|
|
|
class function IsIgnoredComponent(const AOwner: TComponent; const AComponentName: string): Boolean;
|
|
|
|
|
class function GetNameCase(const AComponent: TComponent; const ADefaultValue: TMVCNameCase = ncAsIs): TMVCNameCase; overload;
|
|
|
|
|
class function GetNameCase(const AObject: TObject; const ADefaultValue: TMVCNameCase = ncAsIs): TMVCNameCase; overload;
|
|
|
|
|
class function GetNameAs(const AOwner: TComponent; const AComponentName: string; const ADefaultValue: string): string;
|
2020-04-20 17:56:17 +02:00
|
|
|
|
function GetTypeSerializers: TDictionary<PTypeInfo, IMVCTypeSerializer>;
|
2018-12-12 11:00:41 +01:00
|
|
|
|
procedure RegisterTypeSerializer(const ATypeInfo: PTypeInfo; AInstance: IMVCTypeSerializer);
|
2021-06-11 14:36:47 +02:00
|
|
|
|
function GetObjectTypeOfGenericList(const ATypeInfo: PTypeInfo; out ARttiType: TRttiType): Boolean; overload;
|
|
|
|
|
function GetObjectTypeOfGenericList(const ATypeInfo: PTypeInfo): TClass; overload;
|
2020-09-16 15:56:14 +02:00
|
|
|
|
constructor Create(const Config: TMVCConfig); overload;
|
|
|
|
|
constructor Create; overload;
|
2017-03-28 14:52:13 +02:00
|
|
|
|
destructor Destroy; override;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
|
|
{ TMVCAbstractSerializer }
|
|
|
|
|
|
2018-10-30 13:53:01 +01:00
|
|
|
|
uses
|
|
|
|
|
MVCFramework.Cache,
|
2019-10-14 23:11:08 +02:00
|
|
|
|
MVCFramework.Logger,
|
|
|
|
|
System.SysUtils;
|
2017-07-16 19:36:44 +02:00
|
|
|
|
|
2020-09-16 15:56:14 +02:00
|
|
|
|
constructor TMVCAbstractSerializer.Create(const Config: TMVCConfig);
|
2017-03-28 14:52:13 +02:00
|
|
|
|
begin
|
|
|
|
|
inherited Create;
|
2020-09-16 15:56:14 +02:00
|
|
|
|
FConfig := Config;
|
2017-03-28 14:52:13 +02:00
|
|
|
|
FRttiContext := TRttiContext.Create;
|
|
|
|
|
FTypeSerializers := TDictionary<PTypeInfo, IMVCTypeSerializer>.Create;
|
|
|
|
|
end;
|
|
|
|
|
|
2020-09-16 15:56:14 +02:00
|
|
|
|
constructor TMVCAbstractSerializer.Create;
|
|
|
|
|
begin
|
|
|
|
|
Create(nil);
|
|
|
|
|
end;
|
|
|
|
|
|
2017-03-28 14:52:13 +02:00
|
|
|
|
destructor TMVCAbstractSerializer.Destroy;
|
|
|
|
|
begin
|
|
|
|
|
FTypeSerializers.Free;
|
|
|
|
|
FRttiContext.Free;
|
|
|
|
|
inherited Destroy;
|
|
|
|
|
end;
|
|
|
|
|
|
2022-08-12 10:50:46 +02:00
|
|
|
|
class function TMVCAbstractSerializer.GetNameCase(const AObject: TObject; const ADefaultValue: TMVCNameCase): TMVCNameCase;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
var
|
|
|
|
|
ObjType: TRttiType;
|
|
|
|
|
Att: TCustomAttribute;
|
2022-08-12 10:50:46 +02:00
|
|
|
|
RTTIContext: TRttiContext;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
begin
|
|
|
|
|
Result := ADefaultValue;
|
2022-08-12 10:50:46 +02:00
|
|
|
|
RTTIContext := TRttiContext.Create;
|
|
|
|
|
try
|
|
|
|
|
ObjType := RTTIContext.GetType(AObject.ClassType);
|
|
|
|
|
for Att in ObjType.GetAttributes do
|
|
|
|
|
begin
|
|
|
|
|
if Att is MVCNameCaseAttribute then
|
|
|
|
|
begin
|
|
|
|
|
Exit(MVCNameCaseAttribute(Att).KeyCase);
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
finally
|
|
|
|
|
RTTIContext.Free;
|
|
|
|
|
end;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
end;
|
|
|
|
|
|
2018-10-30 13:53:01 +01:00
|
|
|
|
function TMVCAbstractSerializer.GetDataType(const AOwner: TComponent; const AComponentName: string; const ADefaultValue: TMVCDataType)
|
|
|
|
|
: TMVCDataType;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
var
|
|
|
|
|
ObjType: TRttiType;
|
|
|
|
|
ObjFld: TRttiField;
|
|
|
|
|
Att: TCustomAttribute;
|
|
|
|
|
begin
|
|
|
|
|
Result := ADefaultValue;
|
|
|
|
|
if Assigned(AOwner) then
|
|
|
|
|
begin
|
|
|
|
|
ObjType := GetRttiContext.GetType(AOwner.ClassType);
|
|
|
|
|
ObjFld := ObjType.GetField(AComponentName);
|
|
|
|
|
if Assigned(ObjFld) then
|
|
|
|
|
for Att in ObjFld.GetAttributes do
|
|
|
|
|
if Att is MVCDataSetFieldAttribute then
|
|
|
|
|
Exit(MVCDataSetFieldAttribute(Att).DataType);
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
|
2022-08-12 10:50:46 +02:00
|
|
|
|
class function TMVCAbstractSerializer.GetNameAs(const AOwner: TComponent; const AComponentName, ADefaultValue: string): string;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
var
|
|
|
|
|
ObjType: TRttiType;
|
|
|
|
|
ObjFld: TRttiField;
|
|
|
|
|
Att: TCustomAttribute;
|
2022-08-12 10:50:46 +02:00
|
|
|
|
RTTIContext: TRttiContext;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
begin
|
|
|
|
|
Result := ADefaultValue;
|
|
|
|
|
if Assigned(AOwner) then
|
|
|
|
|
begin
|
2022-08-12 10:50:46 +02:00
|
|
|
|
RTTIContext := TRttiContext.Create;
|
|
|
|
|
try
|
|
|
|
|
ObjType := RTTIContext.GetType(AOwner.ClassType);
|
|
|
|
|
ObjFld := ObjType.GetField(AComponentName);
|
|
|
|
|
if Assigned(ObjFld) then
|
|
|
|
|
begin
|
|
|
|
|
for Att in ObjFld.GetAttributes do
|
|
|
|
|
begin
|
|
|
|
|
if Att is MVCNameAsAttribute then
|
|
|
|
|
begin
|
|
|
|
|
Exit(MVCNameAsAttribute(Att).Name);
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
finally
|
|
|
|
|
RTTIContext.Free;
|
|
|
|
|
end;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
|
2022-08-12 10:50:46 +02:00
|
|
|
|
class function TMVCAbstractSerializer.GetNameCase(const AComponent: TComponent; const ADefaultValue: TMVCNameCase): TMVCNameCase;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
var
|
|
|
|
|
ObjType: TRttiType;
|
|
|
|
|
ObjFld: TRttiField;
|
|
|
|
|
Att: TCustomAttribute;
|
2022-08-12 10:50:46 +02:00
|
|
|
|
RTTIContext: TRttiContext;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
begin
|
|
|
|
|
Result := ADefaultValue;
|
|
|
|
|
if Assigned(AComponent) and Assigned(AComponent.Owner) then
|
|
|
|
|
begin
|
2022-08-12 10:50:46 +02:00
|
|
|
|
RTTIContext := TRttiContext.Create;
|
|
|
|
|
try
|
|
|
|
|
ObjType := RTTIContext.GetType(AComponent.Owner.ClassType);
|
|
|
|
|
ObjFld := ObjType.GetField(AComponent.Name);
|
|
|
|
|
if Assigned(ObjFld) then
|
|
|
|
|
begin
|
|
|
|
|
for Att in ObjFld.GetAttributes do
|
|
|
|
|
begin
|
|
|
|
|
if Att is MVCNameCaseAttribute then
|
|
|
|
|
begin
|
|
|
|
|
Exit(MVCNameCaseAttribute(Att).KeyCase);
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
finally
|
|
|
|
|
RTTIContext.Free;
|
|
|
|
|
end;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
|
2019-10-14 23:11:08 +02:00
|
|
|
|
function TMVCAbstractSerializer.GetObjectTypeOfGenericList(const ATypeInfo: PTypeInfo): TClass;
|
2021-06-11 14:36:47 +02:00
|
|
|
|
var
|
|
|
|
|
LRttiType: TRttiType;
|
|
|
|
|
begin
|
|
|
|
|
if not GetObjectTypeOfGenericList(ATypeInfo, LRttiType) then
|
|
|
|
|
Exit(nil);
|
|
|
|
|
|
|
|
|
|
if LRttiType.IsInstance then
|
|
|
|
|
Result := LRttiType.AsInstance.MetaclassType
|
|
|
|
|
else
|
|
|
|
|
Result := nil;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
function TMVCAbstractSerializer.GetObjectTypeOfGenericList(const ATypeInfo: PTypeInfo; out ARttiType: TRttiType): Boolean;
|
2019-10-14 23:11:08 +02:00
|
|
|
|
|
|
|
|
|
function ExtractGenericArguments(ATypeInfo: PTypeInfo): string;
|
|
|
|
|
var
|
2019-10-31 12:31:41 +01:00
|
|
|
|
LOpen: Integer;
|
|
|
|
|
LClose: Integer;
|
2019-10-14 23:11:08 +02:00
|
|
|
|
LTypeInfoName: string;
|
2020-06-22 23:42:15 +02:00
|
|
|
|
I: Integer;
|
2019-10-14 23:11:08 +02:00
|
|
|
|
begin
|
|
|
|
|
LTypeInfoName := UTF8ToString(ATypeInfo.Name);
|
2019-10-31 12:31:41 +01:00
|
|
|
|
LOpen := Pos('<', LTypeInfoName);
|
2020-06-22 23:42:15 +02:00
|
|
|
|
LClose := Pos('>', LTypeInfoName);
|
2020-06-22 22:54:20 +02:00
|
|
|
|
|
|
|
|
|
if LTypeInfoName.CountChar('>') > 1 then
|
|
|
|
|
begin
|
|
|
|
|
for I := LTypeInfoName.Length - 1 downto 0 do
|
|
|
|
|
begin
|
|
|
|
|
if LTypeInfoName[I] = '>' then
|
|
|
|
|
begin
|
|
|
|
|
LClose := I;
|
|
|
|
|
Break;
|
|
|
|
|
end;
|
|
|
|
|
end;
|
2020-06-22 23:42:15 +02:00
|
|
|
|
end;
|
2019-10-14 23:11:08 +02:00
|
|
|
|
|
2019-10-31 12:31:41 +01:00
|
|
|
|
if LOpen <= 0 then
|
2019-10-14 23:11:08 +02:00
|
|
|
|
Exit('');
|
|
|
|
|
|
2019-10-31 12:31:41 +01:00
|
|
|
|
Result := LTypeInfoName.Substring(LOpen, LClose - LOpen - 1);
|
2019-10-14 23:11:08 +02:00
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
var
|
|
|
|
|
LType: string;
|
2019-10-31 12:31:41 +01:00
|
|
|
|
LGetEnumerator: TRttiMethod;
|
2019-10-14 23:11:08 +02:00
|
|
|
|
begin
|
2019-10-31 12:31:41 +01:00
|
|
|
|
LGetEnumerator := GetRttiContext.GetType(ATypeInfo).GetMethod('GetEnumerator');
|
|
|
|
|
if not Assigned(LGetEnumerator) then
|
|
|
|
|
begin
|
2021-06-11 14:36:47 +02:00
|
|
|
|
ARttiType := nil;
|
|
|
|
|
Exit(False);
|
2019-10-31 12:31:41 +01:00
|
|
|
|
end;
|
2019-10-14 23:11:08 +02:00
|
|
|
|
|
2019-10-31 12:31:41 +01:00
|
|
|
|
LType := ExtractGenericArguments(LGetEnumerator.ReturnType.Handle);
|
2021-06-11 14:36:47 +02:00
|
|
|
|
ARttiType := GetRttiContext.FindType(LType);
|
|
|
|
|
Result := True;
|
2019-10-14 23:11:08 +02:00
|
|
|
|
end;
|
|
|
|
|
|
2017-03-28 14:52:13 +02:00
|
|
|
|
function TMVCAbstractSerializer.GetRttiContext: TRttiContext;
|
|
|
|
|
begin
|
|
|
|
|
Result := FRttiContext;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
function TMVCAbstractSerializer.GetSerializationType(
|
|
|
|
|
const AObject: TObject;
|
|
|
|
|
const ADefaultValue: TMVCSerializationType): TMVCSerializationType;
|
|
|
|
|
var
|
|
|
|
|
ObjType: TRttiType;
|
|
|
|
|
Att: TCustomAttribute;
|
2017-07-16 19:36:44 +02:00
|
|
|
|
lSerializationTypeCacheKey: string;
|
|
|
|
|
lValue: TValue;
|
|
|
|
|
lFound: Boolean;
|
2017-03-28 14:52:13 +02:00
|
|
|
|
begin
|
2021-01-27 20:25:35 +01:00
|
|
|
|
if AObject = nil then
|
|
|
|
|
begin
|
|
|
|
|
Exit(ADefaultValue);
|
|
|
|
|
end;
|
2017-07-16 19:36:44 +02:00
|
|
|
|
lSerializationTypeCacheKey := AObject.QualifiedClassName + '::sertype';
|
|
|
|
|
if TMVCCacheSingleton.Instance.Contains(lSerializationTypeCacheKey, lValue) then
|
|
|
|
|
begin
|
|
|
|
|
if TMVCSerializationType(lValue.AsOrdinal) = stUnknown then
|
|
|
|
|
{ no serializationtype attribute is present in the rtti, just return the default value requested by the user }
|
|
|
|
|
Exit(ADefaultValue)
|
|
|
|
|
else
|
|
|
|
|
Exit(TMVCSerializationType(lValue.AsOrdinal));
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
lFound := False;
|
2017-03-28 14:52:13 +02:00
|
|
|
|
Result := ADefaultValue;
|
|
|
|
|
ObjType := GetRttiContext.GetType(AObject.ClassType);
|
|
|
|
|
for Att in ObjType.GetAttributes do
|
2017-07-16 19:36:44 +02:00
|
|
|
|
begin
|
2017-03-28 14:52:13 +02:00
|
|
|
|
if Att is MVCSerializeAttribute then
|
2017-07-16 19:36:44 +02:00
|
|
|
|
begin
|
|
|
|
|
Result := MVCSerializeAttribute(Att).SerializationType;
|
|
|
|
|
Assert(Result <> stUnknown, 'You cannot use stUnknown in the MVCSerialize attribute. It is for internal use only.');
|
|
|
|
|
{ If the serialization type has been "searched" in the rtti, then we can save the
|
|
|
|
|
result of this expensive search in the cache }
|
|
|
|
|
TMVCCacheSingleton.Instance.SetValue(lSerializationTypeCacheKey, TValue.FromOrdinal(TypeInfo(TMVCSerializationType), Ord(Result)));
|
|
|
|
|
lFound := True;
|
|
|
|
|
Break;
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
{ if no serializationtype attribute found in the type, then we can save in the cache this information
|
|
|
|
|
using the sentinal value stUnknown }
|
|
|
|
|
if not lFound then
|
|
|
|
|
TMVCCacheSingleton.Instance.SetValue(lSerializationTypeCacheKey, TValue.FromOrdinal(TypeInfo(TMVCSerializationType), Ord(stUnknown)));
|
2017-03-28 14:52:13 +02:00
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
function TMVCAbstractSerializer.GetTypeSerializers: TDictionary<PTypeInfo, IMVCTypeSerializer>;
|
|
|
|
|
begin
|
|
|
|
|
Result := FTypeSerializers;
|
|
|
|
|
end;
|
|
|
|
|
|
2022-08-12 10:50:46 +02:00
|
|
|
|
class function TMVCAbstractSerializer.IsIgnoredAttribute(const AAttributes: TMVCIgnoredList; const AName: string): Boolean;
|
2017-03-28 14:52:13 +02:00
|
|
|
|
var
|
|
|
|
|
I: Integer;
|
|
|
|
|
begin
|
|
|
|
|
Result := False;
|
2017-07-16 19:36:44 +02:00
|
|
|
|
for I := low(AAttributes) to high(AAttributes) do
|
2023-08-16 17:15:11 +02:00
|
|
|
|
if AnsiSameText(AAttributes[I], AName) then
|
2017-03-28 14:52:13 +02:00
|
|
|
|
Exit(True);
|
|
|
|
|
end;
|
|
|
|
|
|
2022-08-12 10:50:46 +02:00
|
|
|
|
class function TMVCAbstractSerializer.IsIgnoredComponent(const AOwner: TComponent; const AComponentName: string): Boolean;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
var
|
|
|
|
|
ObjType: TRttiType;
|
|
|
|
|
ObjFld: TRttiField;
|
|
|
|
|
Att: TCustomAttribute;
|
2022-08-12 10:50:46 +02:00
|
|
|
|
RTTIContext: TRttiContext;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
begin
|
|
|
|
|
Result := False;
|
|
|
|
|
if Assigned(AOwner) then
|
|
|
|
|
begin
|
2022-08-12 10:50:46 +02:00
|
|
|
|
RTTIContext := TRttiContext.Create;
|
|
|
|
|
try
|
|
|
|
|
ObjType := RTTIContext.GetType(AOwner.ClassType);
|
|
|
|
|
ObjFld := ObjType.GetField(AComponentName);
|
|
|
|
|
if Assigned(ObjFld) then
|
|
|
|
|
begin
|
|
|
|
|
for Att in ObjFld.GetAttributes do
|
|
|
|
|
begin
|
|
|
|
|
if Att is MVCDoNotSerializeAttribute then
|
|
|
|
|
begin
|
|
|
|
|
Exit(True);
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
finally
|
|
|
|
|
RTTIContext.Free;
|
|
|
|
|
end;
|
2017-03-29 14:49:35 +02:00
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
procedure TMVCAbstractSerializer.RegisterTypeSerializer(const ATypeInfo: PTypeInfo; AInstance: IMVCTypeSerializer);
|
2017-03-28 14:52:13 +02:00
|
|
|
|
begin
|
2019-02-19 13:04:53 +01:00
|
|
|
|
{$IFDEF NEXTGEN}
|
|
|
|
|
LogD('Registering TypeSerializer for: ' + PChar(Pointer(ATypeInfo.Name)));
|
|
|
|
|
{$ELSE}
|
2018-10-30 13:53:01 +01:00
|
|
|
|
LogD('Registering TypeSerializer for: ' + string(ATypeInfo.Name));
|
2019-02-19 13:04:53 +01:00
|
|
|
|
{$ENDIF}
|
|
|
|
|
|
2017-03-28 14:52:13 +02:00
|
|
|
|
FTypeSerializers.AddOrSetValue(ATypeInfo, AInstance);
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
end.
|