Delphi创建JSON [英] Delphi create JSON

查看:150
本文介绍了Delphi创建JSON的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Delphi XE6,我正在寻找创建JSON和解析JSON的最佳方法.

I'm on Delphi XE6 and I search the best way for create JSON and for parse JSON.

我尝试使用REST.Json单元和这种方法:TJson.ObjectToJsonString

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;

结果是

{
    "sNom": "JEAN",
    "ListContrat": {
        "ownsObjects": true,
        "items": [{
            "dDateDeb": 42005,
        }, {
            "dDateDeb": 42370,
        }],
        "count": 2,
        "arrayManager": {}
    }
}

结果是,我具有TObjectList的某些属性(例如"ownsObjects").

In the result I have some property of TObjectList<> (ex "ownsObjects").

这是创建JSON的最佳方法吗? 我必须使用框架吗? 你有一个很好的教程吗?

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.

推荐答案

如果JSON仅用于序列化/反序列化(大多数情况下),则应仅在应用程序的边界上处理JSON.

If the JSON is just for Serializing/Deserializing (most cases) the you should deal with JSON only on the bounderies of your application.

为外部定义您的合同,并使用它们将数据从内部传输到外部,反之亦然.

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.

请参阅:单一责任原则

要在业务对象和合同之间轻松映射,请使用映射器

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.

放在一起

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.

注意 这已在Delphi Seattle上进行了测试-您可能需要更改一些单元才能在XE6上运行

这篇关于Delphi创建JSON的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆