使用TRESTResponseDataSetAdapter将JSON转换为DataSet

时间:2019-07-18 12:57:30

标签: json rest delphi dataset converters

我需要使用一种REST API,该API返回类似于以下内容的JSON:

[
    {
        "purchaseCode": 1,
        "totalItems": 5,
        "totalPrice": 5102.04,
        "deliveryAddress": {
            "name": "Michael Jennette",
            "country": "Brazil",
            "state": "São Paulo",
            "postCode": "16",
            "landmark": ""
        }
    },
    {
        "purchaseCode": 2,
        "totalItems": 3,
        "totalPrice": 4312.65,
        "deliveryAddress": {
            "name": "David Samuel",
            "country": "Brazil",
            "state": "São Paulo",
            "postCode": "40",
            "landmark": ""
        }
    }
]

我可以使用TJSONObject.ParseJSONValue轻松地将此JSON格式转换为类。问题是我需要使用此json的数据打印报告。因此,将JSON转换为DataSet更加有趣。

在JSON存在之前,XML被大量使用。在对象拥有另一个对象的XML中,数据集必须具有一个数据集类型的字段,该字段将由另一个数据集引用。这样就可以将所有XML转换为DataSet。

我正在尝试使用本机组件TRESTResponseDataSetAdapter对JSON进行同样的操作。我已经看到可以为ResponseDataSetAdapter指定字段,并且每个字段都可以具有“子字段”。

因此,我将“ deliveryAddress”定义为DataSet类型并指定其“子字段”。但是,当我运行请求时,出现“ Invalid Argument”错误,然后出现另一个“ Invalid value for field“ deliveryAddress”错误。

是否可以使用TRESTResponseDataSetAdapter将JSON格式转换为DataSet?如果是,怎么办?还是有另一种方法?我不想转换为类,然后手动填充每个数据集。


示例项目:

服务器(WebBroker)

Project1.dpr

program Project1;
{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Types,
  IPPeerServer,
  IPPeerAPI,
  IdHTTPWebBrokerBridge,
  Web.WebReq,
  Web.WebBroker,
  WebModuleUnit1 in 'WebModuleUnit1.pas' {WebModule1: TWebModule},
  ServerConst1 in 'ServerConst1.pas';

{$R *.res}

function BindPort(Aport: Integer): Boolean;
var
  LTestServer: IIPTestServer;
begin
  Result := True;
  try
    LTestServer := PeerFactory.CreatePeer('', IIPTestServer) as IIPTestServer;
    LTestServer.TestOpenPort(APort, nil);
  except
    Result := False;
  end;
end;

function CheckPort(Aport: Integer): Integer;
begin
  if BindPort(Aport) then
    Result := Aport
  else
    Result := 0;
end;

procedure SetPort(const Aserver: TIdHTTPWebBrokerBridge; APort: String);
begin
  if not (Aserver.Active) then
  begin
    APort := APort.Replace(cCommandSetPort, '').Trim;
    if CheckPort(APort.ToInteger) > 0 then
    begin
      Aserver.DefaultPort := APort.ToInteger;
      Writeln(Format(sPortSet, [APort]));
    end
    else
      Writeln(Format(sPortInUse, [Aport]));
  end
  else
    Writeln(sServerRunning);
  Write(cArrow);
end;

procedure StartServer(const Aserver: TIdHTTPWebBrokerBridge);
begin
  if not (Aserver.Active) then
  begin
    if CheckPort(Aserver.DefaultPort) > 0 then
    begin
      Writeln(Format(sStartingServer, [Aserver.DefaultPort]));
      Aserver.Bindings.Clear;
      Aserver.Active := True;
    end
    else
      Writeln(Format(sPortInUse, [Aserver.DefaultPort.ToString]));
  end
  else
    Writeln(sServerRunning);
  Write(cArrow);
end;

procedure StopServer(const Aserver: TIdHTTPWebBrokerBridge);
begin
  if Aserver.Active  then
  begin
    Writeln(sStoppingServer);
    Aserver.Active := False;
    Aserver.Bindings.Clear;
    Writeln(sServerStopped);
  end
  else
    Writeln(sServerNotRunning);
  Write(cArrow);
end;

procedure WriteCommands;
begin
  Writeln(sCommands);
  Write(cArrow);
end;

procedure WriteStatus(const Aserver: TIdHTTPWebBrokerBridge);
begin
  Writeln(sIndyVersion + Aserver.SessionList.Version);
  Writeln(sActive + Aserver.Active.ToString(TUseBoolStrs.True));
  Writeln(sPort + Aserver.DefaultPort.ToString);
  Writeln(sSessionID + Aserver.SessionIDCookieName);
  Write(cArrow);
end;

procedure RunServer(APort: Integer);
var
  LServer: TIdHTTPWebBrokerBridge;
  LResponse: string;
begin
  WriteCommands;
  LServer := TIdHTTPWebBrokerBridge.Create(nil);
  try
    LServer.DefaultPort := APort;
    while True do
    begin
      Readln(LResponse);
      LResponse := LowerCase(LResponse);
      if LResponse.StartsWith(cCommandSetPort) then
        SetPort(LServer, LResponse)
      else if sametext(LResponse, cCommandStart) then
        StartServer(LServer)
      else if sametext(LResponse, cCommandStatus) then
        WriteStatus(LServer)
      else if sametext(LResponse, cCommandStop) then
        StopServer(LServer)
      else if sametext(LResponse, cCommandHelp) then
        WriteCommands
      else if sametext(LResponse, cCommandExit) then
        if LServer.Active then
        begin
          StopServer(LServer);
          break
        end
        else
          break
      else
      begin
        Writeln(sInvalidCommand);
        Write(cArrow);
      end;
    end;
  finally
    LServer.Free;
  end;
end;

begin
  try
  if WebRequestHandler <> nil then
    WebRequestHandler.WebModuleClass := WebModuleClass;
    RunServer(8080);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end
end.

ServerConst1.pas

unit ServerConst1;

interface

resourcestring
  sPortInUse = '- Error: Port %s already in use';
  sPortSet = '- Port set to %s';
  sServerRunning = '- The Server is already running';
  sStartingServer = '- Starting HTTP Server on port %d';
  sStoppingServer = '- Stopping Server';
  sServerStopped = '- Server Stopped';
  sServerNotRunning = '- The Server is not running';
  sInvalidCommand = '- Error: Invalid Command';
  sIndyVersion = '- Indy Version: ';
  sActive = '- Active: ';
  sPort = '- Port: ';
  sSessionID = '- Session ID CookieName: ';
  sCommands = 'Enter a Command: ' + slineBreak +
    '   - "start" to start the server'+ slineBreak +
    '   - "stop" to stop the server'+ slineBreak +
    '   - "set port" to change the default port'+ slineBreak +
    '   - "status" for Server status'+ slineBreak +
    '   - "help" to show commands'+ slineBreak +
    '   - "exit" to close the application';

const
  cArrow = '->';
  cCommandStart = 'start';
  cCommandStop = 'stop';
  cCommandStatus = 'status';
  cCommandHelp = 'help';
  cCommandSetPort = 'set port';
  cCommandExit = 'exit';

implementation

end.

WebModuleUnit1.pas

unit WebModuleUnit1;

interface

uses System.SysUtils, System.Classes, Web.HTTPApp;

type
  TWebModule1 = class(TWebModule)
    procedure WebModule1jsonAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  WebModuleClass: TComponentClass = TWebModule1;

implementation

{%CLASSGROUP 'System.Classes.TPersistent'}

{$R *.dfm}


procedure TWebModule1.WebModule1jsonAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  Response.ContentType := 'application/json; charset=utf-8';
  Response.Content :=
    '[{"purchaseCode":1,"totalItems":5,"totalPrice":5102.04,"deliveryAddress":{"name":"Michael Jennette","country":"Brazil","state":"S\u00E3o Paulo","postCode":"16","landmark":""}},' +
    '{"purchaseCode":2,"totalItems":3,"totalPrice":4312.65,"deliveryAddress":{"name":"David Samuel","country":"Brazil","state":"S\u00E3o Paulo","postCode":"40","landmark":""}}]';
end;

end.

WebModuleUnit1.dfm

object WebModule1: TWebModule1
  OldCreateOrder = False
  Actions = <
    item
      MethodType = mtGet
      Name = 'json'
      PathInfo = '/json'
      OnAction = WebModule1jsonAction
    end>
  Height = 230
  Width = 415
end

客户

Client1.dpr

program Client1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Unit1.pas

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, IPPeerClient, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param,
  FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Comp.DataSet, FireDAC.Comp.Client,
  REST.Response.Adapter, REST.Client, Data.Bind.Components, Data.Bind.ObjectScope, Vcl.Grids, Vcl.DBGrids, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    btnRequest: TButton;
    DBGrid1: TDBGrid;
    DataSource1: TDataSource;
    RESTClient1: TRESTClient;
    RESTRequest1: TRESTRequest;
    RESTResponse1: TRESTResponse;
    RESTResponseDataSetAdapter1: TRESTResponseDataSetAdapter;
    FDMemTable1: TFDMemTable;
    FDMemTable2: TFDMemTable;
    FDMemTable1purchaseCode: TIntegerField;
    FDMemTable1totalItems: TIntegerField;
    FDMemTable1totalPrice: TCurrencyField;
    FDMemTable1deliveryAddress: TDataSetField;
    FDMemTable2name: TStringField;
    FDMemTable2country: TStringField;
    FDMemTable2state: TStringField;
    FDMemTable2postCode: TStringField;
    FDMemTable2landmark: TStringField;
    procedure btnRequestClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnRequestClick(Sender: TObject);
begin
  RESTRequest1.Execute;
end;

end.

Unit1.dfm

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 362
  ClientWidth = 724
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object btnRequest: TButton
    Left = 24
    Top = 24
    Width = 75
    Height = 25
    Caption = 'Request'
    TabOrder = 0
    OnClick = btnRequestClick
  end
  object DBGrid1: TDBGrid
    Left = 296
    Top = 48
    Width = 409
    Height = 297
    DataSource = DataSource1
    TabOrder = 1
    TitleFont.Charset = DEFAULT_CHARSET
    TitleFont.Color = clWindowText
    TitleFont.Height = -11
    TitleFont.Name = 'Tahoma'
    TitleFont.Style = []
  end
  object DataSource1: TDataSource
    DataSet = FDMemTable1
    Left = 232
    Top = 152
  end
  object RESTClient1: TRESTClient
    Accept = 'application/json, text/plain; q=0.9, text/html;q=0.8,'
    AcceptCharset = 'UTF-8, *;q=0.8'
    BaseURL = 'http://localhost:8080/json'
    Params = <>
    HandleRedirects = True
    RaiseExceptionOn500 = False
    Left = 32
    Top = 64
  end
  object RESTRequest1: TRESTRequest
    Client = RESTClient1
    Params = <>
    Response = RESTResponse1
    SynchronizedEvents = False
    Left = 32
    Top = 112
  end
  object RESTResponse1: TRESTResponse
    ContentType = 'application/json'
    Left = 32
    Top = 160
  end
  object RESTResponseDataSetAdapter1: TRESTResponseDataSetAdapter
    Dataset = FDMemTable1
    FieldDefs = <
      item
        Name = 'purchaseCode'
        DataType = ftInteger
      end
      item
        Name = 'totalItems'
        DataType = ftInteger
      end
      item
        Name = 'totalPrice'
        DataType = ftCurrency
      end
      item
        Name = 'deliveryAddress'
        ChildDefs = <
          item
            Name = 'name'
            DataType = ftString
            Size = 150
          end
          item
            Name = 'country'
            DataType = ftString
            Size = 150
          end
          item
            Name = 'state'
            DataType = ftString
            Size = 150
          end
          item
            Name = 'postCode'
            DataType = ftString
            Size = 50
          end
          item
            Name = 'landmark'
            DataType = ftString
            Size = 150
          end>
        DataType = ftDataSet
        Size = 5
      end>
    Response = RESTResponse1
    Left = 32
    Top = 216
  end
  object FDMemTable1: TFDMemTable
    FetchOptions.AssignedValues = [evMode]
    FetchOptions.Mode = fmAll
    ResourceOptions.AssignedValues = [rvSilentMode]
    ResourceOptions.SilentMode = True
    UpdateOptions.AssignedValues = [uvCheckRequired]
    UpdateOptions.CheckRequired = False
    Left = 168
    Top = 152
    object FDMemTable1purchaseCode: TIntegerField
      FieldName = 'purchaseCode'
    end
    object FDMemTable1totalItems: TIntegerField
      FieldName = 'totalItems'
    end
    object FDMemTable1totalPrice: TCurrencyField
      FieldName = 'totalPrice'
    end
    object FDMemTable1deliveryAddress: TDataSetField
      FieldName = 'deliveryAddress'
    end
  end
  object FDMemTable2: TFDMemTable
    FetchOptions.AssignedValues = [evMode]
    FetchOptions.Mode = fmAll
    ResourceOptions.AssignedValues = [rvSilentMode]
    ResourceOptions.SilentMode = True
    UpdateOptions.AssignedValues = [uvCheckRequired]
    UpdateOptions.CheckRequired = False
    Left = 168
    Top = 224
    object FDMemTable2name: TStringField
      FieldName = 'name'
      Size = 150
    end
    object FDMemTable2country: TStringField
      FieldName = 'country'
      Size = 150
    end
    object FDMemTable2state: TStringField
      FieldName = 'state'
      Size = 150
    end
    object FDMemTable2postCode: TStringField
      FieldName = 'postCode'
      Size = 50
    end
    object FDMemTable2landmark: TStringField
      FieldName = 'landmark'
      Size = 150
    end
  end
end

0 个答案:

没有答案