问题
I'm on Delphi XE6 and I search the best way for create JSON and for parse JSON.
I try to work with REST.Json unit and this method : TJson.ObjectToJsonString
TContrat = class
private
FdDateDeb: TDate;
public
property dDateDeb: TDate read FdDateDeb write FdDateDeb;
end;
TApprenant = class
private
FsNom : string;
[JSONName('ListContrat')]
FListContrat: TObjectList<TContrat>;
public
property sNom : string read FsNom write FsNom;
property ListContrat: TObjectList<TContrat> read FListContrat write FListContrat;
end;
...
procedure TForm3.Button2Click(Sender: TObject);
var
apprenant : TApprenant;
contrat : TContrat;
begin
Memo1.Clear;
apprenant := TApprenant.Create;
apprenant.sNom := 'JEAN';
contrat := TContrat.Create;
contrat.dDateDeb := StrToDate('01/01/2015');
apprenant.ListContrat.Add(contrat);
contrat := TContrat.Create;
contrat.dDateDeb := StrToDate('01/01/2016');
apprenant.ListContrat.Add(contrat);
Memo1.Lines.Add(TJson.ObjectToJsonString(apprenant));
end;
the result is
{
"sNom": "JEAN",
"ListContrat": {
"ownsObjects": true,
"items": [{
"dDateDeb": 42005,
}, {
"dDateDeb": 42370,
}],
"count": 2,
"arrayManager": {}
}
}
In the result I have some property of TObjectList<> (ex "ownsObjects").
Is it the best way to create a JSON ? I must use a framework ? Have you a good tutorial ?
Sorry, I have search on forum but not found a good way.
回答1:
If the JSON is just for Serializing/Deserializing (most cases) the you should deal with JSON only on the bounderies of your application.
Contracts
Define your contract(s) for the outside and use them to transport the data from inside to outside and vice versa.
First the contract unit which is designed for a convenient de-/serialization
unit whatever.ApiJson.v1;
// this is the contract definition for version 1
interface
uses
System.SysUtils,
REST.Json.Types,
Commons.JsonContract;
type
TApprenantJSON = class;
TContratJSON = class;
TContratJSON = class( TJsonContractBase )
private
[ JSONName( 'date_deb' ) ]
FDateDeb: TDateTime;
public
property DateDeb: TDateTime read FDateDeb write FDateDeb;
public
class function FromJson( const aJsonStr: string ): TContratJSON;
end;
TApprenantJSON = class( TJsonContractBase )
private
[ JSONName( 'nom' ) ]
FNom: string;
[ JSONName( 'contrats' ) ]
FContrats: TArray<TContratJSON>;
public
property Nom : string read FNom write FNom;
property Contrats: TArray<TContratJSON> read FContrats write FContrats;
public
destructor Destroy; override;
public
class function FromJson( const aJsonStr: string ): TApprenantJSON;
end;
implementation
{ TApprenantJSON }
destructor TApprenantJSON.Destroy;
begin
DisposeObjectArray<TContratJSON>( FContrats );
inherited;
end;
class function TApprenantJSON.FromJson( const aJsonStr: string ): TApprenantJSON;
begin
Result := _FromJson( aJsonStr ) as TApprenantJSON;
end;
{ TContratJSON }
class function TContratJSON.FromJson( const aJsonStr: string ): TContratJSON;
begin
Result := _FromJson( aJsonStr ) as TContratJSON;
end;
end.
As you can see I use arrays and classes. To manage these arrays with classes I have a base class dealing with that
unit Commons.JsonContract;
interface
type
TJsonContractBase = class abstract
protected
procedure DisposeObjectArray<T: class>( var arr: TArray<T> );
class function _FromJson( const aJsonStr: string ): TObject; overload;
class procedure _FromJson( aResult: TObject; const aJsonStr: string ); overload;
public
function ToJson( ): string; virtual;
end;
implementation
uses
System.Sysutils,
System.JSON,
REST.JSON;
{ TJsonContractBase }
procedure TJsonContractBase.DisposeObjectArray<T>( var arr: TArray<T> );
var
I: Integer;
begin
for I := low( arr ) to high( arr ) do
FreeAndNil( arr[ I ] );
SetLength( arr, 0 );
end;
class function TJsonContractBase._FromJson( const aJsonStr: string ): TObject;
begin
Result := Self.Create;
try
_FromJson( Result, aJsonStr );
except
Result.Free;
raise;
end;
end;
class procedure TJsonContractBase._FromJson( aResult: TObject; const aJsonStr: string );
var
lJson: TJsonObject;
begin
lJson := TJsonObject.ParseJSONValue( aJsonStr ) as TJsonObject;
try
TJson.JsonToObject( aResult, lJson );
finally
lJson.Free;
end;
end;
function TJsonContractBase.ToJson: string;
begin
Result := TJson.ObjectToJsonString( Self );
end;
end.
Business Objects
For the application itself we use this classes only for de-/serialization. The internal business objects/entities are separated from them.
unit whatever.DataObjects;
interface
uses
System.Generics.Collections;
type
TApprenant = class;
TContrat = class;
TApprenant = class
private
FNom : string;
FContrats: TObjectList<TContrat>;
public
property Nom : string read FNom write FNom;
property Contrats: TObjectList<TContrat> read FContrats;
public
constructor Create;
destructor Destroy; override;
end;
TContrat = class
private
FDateDeb: TDateTime;
public
property DateDeb: TDateTime read FDateDeb write FDateDeb;
end;
implementation
{ TApprenant }
constructor TApprenant.Create;
begin
inherited;
FContrats := TObjectList<TContrat>.Create( true );
end;
destructor TApprenant.Destroy;
begin
FContrats.Free;
inherited;
end;
end.
What is the benefit declare everything twice?
Well, now you can change the business objects or contracts without infecting each other. You can have different types, names in both and your internal classes are not tight bound to any contract to the outside.
See: Single Responsibility Principle
Mapping
For an easy mapping between the business objects and the contract use a mapper
unit Commons.Mappings;
interface
uses
System.Generics.Collections,
System.Rtti,
System.SysUtils,
System.TypInfo;
type
TMapKey = record
Source: PTypeInfo;
Target: PTypeInfo;
class function Create<TSource, TTarget>( ): TMapKey; static;
end;
TMapper = class
private
FMappings: TDictionary<TMapKey, TFunc<TValue, TValue>>;
public
procedure Add<TSource, TTarget>( aConverter: TFunc<TSource, TTarget> ); overload;
procedure Add<TSource, TTarget>( aConverter: TFunc<TSource, TTarget>; aReverter: TFunc<TTarget, TSource> ); overload;
public
constructor Create;
destructor Destroy; override;
function Map<TSource, TTarget>( const aSource: TSource ): TTarget; overload;
procedure Map<TSource, TTarget>( const aSource: TSource; out aTarget: TTarget ); overload;
function MapCollection<TSource, TTarget>( const aCollection: TEnumerable<TSource> ): TArray<TTarget>; overload;
function MapCollection<TSource, TTarget>( const aCollection: array of TSource ): TArray<TTarget>; overload;
end;
implementation
{ TMapper }
procedure TMapper.Add<TSource, TTarget>( aConverter: TFunc<TSource, TTarget> );
var
lKey: TMapKey;
begin
lKey := TMapKey.Create<TSource, TTarget>( );
FMappings.Add( lKey,
function( Source: TValue ): TValue
begin
Result := TValue.From<TTarget>( aConverter( Source.AsType<TSource>( ) ) );
end );
end;
procedure TMapper.Add<TSource, TTarget>(
aConverter: TFunc<TSource, TTarget>;
aReverter : TFunc<TTarget, TSource> );
begin
Add<TSource, TTarget>( aConverter );
Add<TTarget, TSource>( aReverter );
end;
constructor TMapper.Create;
begin
inherited;
FMappings := TDictionary < TMapKey, TFunc < TValue, TValue >>.Create;
end;
destructor TMapper.Destroy;
begin
FMappings.Free;
inherited;
end;
function TMapper.Map<TSource, TTarget>( const aSource: TSource ): TTarget;
var
lKey: TMapKey;
begin
lKey := TMapKey.Create<TSource, TTarget>( );
Result := FMappings[ lKey ]( TValue.From<TSource>( aSource ) ).AsType<TTarget>( );
end;
procedure TMapper.Map<TSource, TTarget>(
const aSource: TSource;
out aTarget : TTarget );
begin
aTarget := Map<TSource, TTarget>( aSource );
end;
function TMapper.MapCollection<TSource, TTarget>( const aCollection: array of TSource ): TArray<TTarget>;
var
lCollection: TList<TSource>;
begin
lCollection := TList<TSource>.Create( );
try
lCollection.AddRange( aCollection );
Result := MapCollection<TSource, TTarget>( lCollection );
finally
lCollection.Free;
end;
end;
function TMapper.MapCollection<TSource, TTarget>( const aCollection: TEnumerable<TSource> ): TArray<TTarget>;
var
lKey : TMapKey;
lMapHandler: TFunc<TValue, TValue>;
lResult : TList<TTarget>;
lSourceItem: TSource;
begin
lKey := TMapKey.Create<TSource, TTarget>( );
lMapHandler := FMappings[ lKey ];
lResult := TList<TTarget>.Create;
try
for lSourceItem in aCollection do
begin
lResult.Add( lMapHandler( TValue.From<TSource>( lSourceItem ) ).AsType<TTarget>( ) );
end;
Result := lResult.ToArray( );
finally
lResult.Free;
end;
end;
{ TMapKey }
class function TMapKey.Create<TSource, TTarget>: TMapKey;
begin
Result.Source := TypeInfo( TSource );
Result.Target := TypeInfo( TTarget );
end;
end.
Putting all together
program so_37659536;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Commons.Mappings in 'Commons.Mappings.pas',
Commons.JsonContract in 'Commons.JsonContract.pas',
whatever.DataObjects in 'whatever.DataObjects.pas',
whatever.ApiJson.v1 in 'whatever.ApiJson.v1.pas',
whatever.ApiJson.v2 in 'whatever.ApiJson.v2.pas';
procedure DemoMapV1( aMapper: TMapper );
var
lApprenant: TApprenant;
lContrat : TContrat;
lApprenantJSON: whatever.ApiJson.v1.TApprenantJSON;
lApprenantJSONStr: string;
begin
WriteLn;
WriteLn( 'V1' );
WriteLn;
{$REGION 'Serialize'}
lApprenantJSON := nil;
try
lApprenant := TApprenant.Create;
try
lApprenant.Nom := 'JEAN';
lContrat := TContrat.Create;
lContrat.DateDeb := EncodeDate( 2015, 1, 1 );
lApprenant.Contrats.Add( lContrat );
aMapper.Map( lApprenant, lApprenantJSON );
finally
lApprenant.Free;
end;
lApprenantJSONStr := lApprenantJSON.ToJson( );
finally
lApprenantJSON.Free;
end;
{$ENDREGION 'Serialize'}
WriteLn( lApprenantJSONStr );
{$REGION 'Deserialize'}
lApprenant := nil;
lApprenantJSON := whatever.ApiJson.v1.TApprenantJSON.FromJson( lApprenantJSONStr );
try
aMapper.Map( lApprenantJSON, lApprenant );
try
WriteLn( 'Nom: ', lApprenant.Nom );
WriteLn( 'Contrats:' );
for lContrat in lApprenant.Contrats do
begin
WriteLn( '- ', DateToStr( lContrat.DateDeb ) );
end;
finally
lApprenant.Free;
end;
finally
lApprenantJSON.Free;
end;
{$ENDREGION 'Deserialize'}
end;
var
Mapper: TMapper;
begin
try
Mapper := TMapper.Create;
try
{$REGION 'Define Mapping'}
{$REGION 'v1'}
Mapper.Add<TApprenant, whatever.ApiJson.v1.TApprenantJSON>(
function( s: TApprenant ): whatever.ApiJson.v1.TApprenantJSON
begin
Result := whatever.ApiJson.v1.TApprenantJSON.Create;
Result.Nom := s.Nom;
Result.Contrats := Mapper.MapCollection<TContrat, whatever.ApiJson.v1.TContratJSON>( s.Contrats );
end,
function( s: whatever.ApiJson.v1.TApprenantJSON ): TApprenant
begin
Result := TApprenant.Create;
Result.Nom := s.Nom;
Result.Contrats.AddRange( Mapper.MapCollection<whatever.ApiJson.v1.TContratJSON, TContrat>( s.Contrats ) );
end );
Mapper.Add<TContrat, whatever.ApiJson.v1.TContratJSON>(
function( s: TContrat ): whatever.ApiJson.v1.TContratJSON
begin
Result := whatever.ApiJson.v1.TContratJSON.Create;
Result.DateDeb := s.DateDeb;
end,
function( s: whatever.ApiJson.v1.TContratJSON ): TContrat
begin
Result := TContrat.Create;
Result.DateDeb := s.DateDeb;
end );
{$ENDREGION 'v1'}
{$REGION 'v2'}
// mapping for v2
{$ENDREGION 'v2'}
{$ENDREGION 'Define Mapping'}
DemoMapV1( Mapper );
finally
Mapper.Free;
end;
except
on E: Exception do
WriteLn( E.ClassName, ': ', E.Message );
end;
ReadLn;
end.
Note This is tested on Delphi Seattle - you may have to change some of the units to get this running on XE6
来源:https://stackoverflow.com/questions/37659536/delphi-create-json