Перейти к содержанию
Fire Monkey от А до Я
  • 0

Как проанализировать json из websocket для отображения данных в реальном времени? (How to parse json from websocket to display realtime data?)


Legit Coder

Вопрос

Привет, Я пытаюсь создать клиент для повторного получения данных в реальном времени из "twelvedata.com " это возвращается с сервера websocket. Вот строка JSON

2022-12-13T22:43:30.142Z <== {"event":"subscribe-status","status":"ok","success":[{"symbol":"USD/JPY","exchange":"PHYSICAL CURRENCY","mic_code":"PHYSICAL CURRENCY","country":"","type":"Physical Currency"},{"symbol":"BTC/USD","exchange":"Coinbase Pro","mic_code":"Coinbase Pro","country":"","type":"Digital Currency"},{"symbol":"ETH/BTC","exchange":"Huobi","mic_code":"Huobi","country":"","type":"Digital Currency"}],"fails":null}

2022-12-13T22:43:30.541Z <== {"event":"price","symbol":"BTC/USD","currency_base":"Bitcoin","currency_quote":"US Dollar","exchange":"Coinbase Pro","type":"Digital Currency","timestamp":1670971410,"price":17722.7,"bid":17722.7,"ask":17722.7,"day_volume":38419}

Я написал клиентский код Delphi, как показано ниже, чтобы получить объект Json, но он вернул статическое значение, и программа зависла.

Спойлер

 

unit UnitWebsocket;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  FMX.Controls.Presentation, FMX.Layouts, FMX.Objects, FMX.Memo.Types, Skia.FMX,
  FMX.ScrollBox, FMX.Memo, System.IOUtils, Ics.Fmx.OverbyteIcsWndControl,
  Ics.Fmx.OverbyteIcsWSocket, Bird.Socket.Client, FMX.WebBrowser;

const
  // API  = 'wss://ws.twelvedata.com/v1/quotes/price?apikey=key';
  API  = '';

type
  TMainUnit = class(TForm)
    Rectangle1: TRectangle;
    GridPanelLayout1: TGridPanelLayout;
    GridPanelLayout2: TGridPanelLayout;
    GridPanelLayout3: TGridPanelLayout;
    Connection: TLabel;
    OpenConnection: TCornerButton;
    CloseConnection: TCornerButton;
    Messages: TLabel;
    Subscribe: TCornerButton;
    Unsubscribe: TCornerButton;
    Reset: TCornerButton;
    Layout1: TLayout;
    Memo1: TMemo;
    Send: TCornerButton;
    Timer1: TTimer;
    GridPanelLayout4: TGridPanelLayout;
    GridPanelLayout5: TGridPanelLayout;
    Price: TLabel;
    Beautify: TCornerButton;
    Layout2: TLayout;
    DisplayMemo: TMemo;
    WebBrowser1: TWebBrowser;
    Splitter1: TSplitter;
    procedure SubscribeClick(Sender: TObject);
    procedure UnsubscribeClick(Sender: TObject);
    procedure ResetClick(Sender: TObject);
    procedure SendClick(Sender: TObject);
    procedure CloseConnectionClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure OpenConnectionClick(Sender: TObject);
//    procedure BeautifyClick(Sender: TObject);
//    procedure LoadCode(const ACode, AType: string; Wrapped: Boolean = False);
//    procedure LoadError(const Msg: string);
  private
    { Dclarations prives }
    FBirdSocket: TBirdSocketClient;
    procedure StartCources;
    procedure Display(Msg : String);
    procedure SocketText(Sender: TObject; const iText: String);
    procedure FCources(const rowCount, colCount, srowCount, scolCount: integer);
    procedure Timer1Timer(Sender: TObject);
  public
    { Dclarations publiques }
  end;

var
  MainUnit: TMainUnit;

implementation

{$R *.fmx}

uses
  System.NetEncoding, System.Rtti, System.Math, System.JSON.Types, System.JSON.Builders, System.JSON.Readers, Common, Converters;

procedure TMainUnit.CloseConnectionClick(Sender: TObject);
begin
  try
    if not Assigned(FBirdSocket) then
      Exit;
    if FBirdSocket.Connected then
      FBirdSocket.Disconnect;
    FreeAndNil(FBirdSocket);
    DisplayMemo.Lines.Add('Websocket connection closed!');
//    Timer1.Enabled := False;
    CloseConnection.Enabled := False;
    Send.Enabled := False;
    OpenConnection.Enabled := True;
  except
    on E:Exception do
      Display(E.Message);
  end
end;

{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ Display a message in our display memo. Delete lines to be sure to not     }
{ overflow the memo which may have a limited capacity.                      }
procedure TMainUnit.Display(Msg : String);
var
  I : Integer;
begin
  DisplayMemo.Lines.BeginUpdate;
  try
    if DisplayMemo.Lines.Count > 200 then begin
      for I := 1 to 50 do
        DisplayMemo.Lines.Delete(0);
    end;
    DisplayMemo.Lines.Add(Format('%s <== %s', [FormatDateTime('dd/mm/yyyy hh:mm:ss', Now), Msg]));
  finally
    DisplayMemo.Lines.EndUpdate;
    DisplayMemo.GoToTextEnd;
  end;
end;

procedure TMainUnit.FCources(const rowCount, colCount, srowCount, scolCount: integer);
var
  i,j: integer;
  trend: TSkSvg;
  aRect: TRectangle;
  bRect: TRectangle;
  aGridP: TGridPanelLayout;
  fLabel: TLabel;
  aControl, bControl, cControl, dControl : TControl;
begin


  GridPanelLayout5.RowCollection.BeginUpdate;
  GridPanelLayout5.ColumnCollection.BeginUpdate;

  GridPanelLayout5.ControlCollection.Clear;
  GridPanelLayout5.RowCollection.Clear;
  GridPanelLayout5.ColumnCollection.Clear;
  
  for i := 0 to -1 + GridPanelLayout5.ControlsCount do
    GridPanelLayout5.Controls[0].Free;

  for i := 1 to rowCount do
    with GridPanelLayout5.RowCollection.Add do
    begin
      SizeStyle := TGridPanelLayout.TSizeStyle.Percent;
      Value := 100 / rowCount; //have cells evenly distributed
    end;

  for i := 1 to colCount do
    with GridPanelLayout5.ColumnCollection.Add do
    begin
      SizeStyle := TGridPanelLayout.TSizeStyle.Percent;
      Value := 100 / colCount; //have cells evenly distributed
    end;

  for i := 0 to -1 + rowCount * colCount do
  begin
    aRect := TRectangle.Create(self);
    aRect.Parent := GridPanelLayout5; //magic: place in the next empty cell
    aRect.Align := TAlignLayout.Client;
    aRect.Visible := true;
    aRect.Name := 'FCource' + IntToStr(i);
    aRect.Fill.Color := TAlphaColorRec.White;
    aRect.Margins.Rect := TRectF.Create(1,1,1,1);
    aRect.Stroke.Color := $FFE5E4E3;
    aRect.Stroke.Thickness := 0.7;
    aRect.XRadius := 7;
    aRect.YRadius := 7;

    bRect := TRectangle.Create(self);
    bRect.Parent := aRect;
    bRect.Align := TAlignLayout.left;
    bRect.Name := 'FTrend' + IntToStr(i);
    bRect.Fill.Color := $FFE6F4EA;
    bRect.Margins.Rect := TRectF.Create(3,3,3,3);
    bRect.Stroke.Kind := TBrushKind.None;
    bRect.Height := 30;
    bRect.Width := 30;
    bRect.XRadius := 7;
    bRect.YRadius := 7;

    trend := TSkSvg.Create(Self);
    trend.Svg.Source := TFile.ReadAllText('Assets\Trendup.svg'); // throws error, so put the file inside bin folder
    trend.Parent := bRect;
    trend.Align := TAlignLayout.Center;
    trend.Svg.OverrideColor := $FF137333;
    trend.Height := 21;
    trend.Width := 21;

    aGridP := TGridPanelLayout.Create(Self);
    with aGridP do
    begin
      aGridP.Parent := aRect;
      aGridP.Align := TAlignLayout.Client;
      aGridP.Margins.Left := 3;

      aGridP.RowCollection.BeginUpdate;
      aGridP.ColumnCollection.BeginUpdate;

      aGridP.ControlCollection.Clear;
      aGridP.RowCollection.Clear;
      aGridP.ColumnCollection.Clear;

      try
        // Make a loop in the columns using Avisoff and this seems to work in all platforms
        for j := 0 to -1 + aGridP.ControlsCount do
          aGridP.ControlCollection.Controls[j,0].DisposeOf;
        for j := 1 to srowCount do
          with aGridP.RowCollection.Add do
          begin
            SizeStyle := TGridPanelLayout.TSizeStyle.Percent;
            Value := 100 / srowCount; //have cells evenly distributed
          end;
        for j := 1 to colCount do
          with aGridP.ColumnCollection.Add do
          begin
            SizeStyle := TGridPanelLayout.TSizeStyle.Percent;
          end;
          // have specified cells values
          aGridP.ColumnCollection[0].Value:=60;
          aGridP.ColumnCollection[1].Value:=40;

        {$REGION 'Region loop'}
        for j := 0 to -1 + srowCount * scolCount do
        begin
          fLabel := TLabel.Create(Self);
          fLabel.Parent := aGridP;
          fLabel.Align :=  TAlignLayout.Client;
          // fLabel.AutoSize :=  true;
          fLabel.Margins.Right := 3;
          fLabel.Margins.Left := 3;
          fLabel.Margins.Top := 3;
          fLabel.Margins.Bottom := 3;
          aControl := aGridP.ControlCollection.Controls[0,0];
          if Assigned(aControl) AND (aControl IS TLabel) then
          begin
            TLabel(aControl).StyledSettings := TLabel(aControl).StyledSettings - [TStyledSetting.Style];
            TLabel(aControl).TextSettings.Font.Style := TLabel(aControl).TextSettings.Font.Style + [TFontStyle.fsBold];
            TLabel(aControl).Text := 'FTicker' + IntToStr(aGridP.ControlCollection.IndexOf(aControl));
          end;

          bControl := aGridP.ControlCollection.Controls[0,1];
          if Assigned(bControl) AND (bControl IS TLabel) then
          begin
            TLabel(bControl).Text := 'FxValue' + IntToStr(aGridP.ControlCollection.IndexOf(bControl));
          end;

          cControl := aGridP.ControlCollection.Controls[1,0];
          if Assigned(cControl) AND (cControl IS TLabel) then
          begin
            TLabel(cControl).Text := 'Percantage' + IntToStr(aGridP.ControlCollection.IndexOf(cControl));
            TLabel(cControl).TextAlign := TTextAlign.Trailing
          end;

          dControl := aGridP.ControlCollection.Controls[1,1];
          if Assigned(dControl) AND (dControl IS TLabel) then
          begin
            TLabel(dControl).Text := 'Change' + IntToStr(aGridP.ControlCollection.IndexOf(dControl));
            TLabel(dControl).TextAlign := TTextAlign.Trailing
          end;
        end;
        {$ENDREGION 'Region loop'}

        {$REGION 'Region JSONIterator'}

        {$ENDREGION 'Region JSONIterator'}

      finally
        aGridP.RowCollection.EndUpdate;
        aGridP.ColumnCollection.EndUpdate;
      end;
    end;
  end;

  GridPanelLayout5.RowCollection.EndUpdate;
  GridPanelLayout5.ColumnCollection.EndUpdate;
end;

procedure TMainUnit.StartCources;
begin
  //
end;

procedure TMainUnit.FormCreate(Sender: TObject);
begin
  {$IFDEF MSWINDOWS}
    ReportMemoryLeaksOnShutdown := (DebugHook <> 0);
  {$ENDIF}
  //  FBirdSocket.HeartBeatInterval := 17;

  Timer1 := TTimer.Create(Self);
  Timer1.Interval := 10000;
  Timer1.OnTimer := Timer1Timer;
  Timer1.Enabled := False;
  CloseConnection.Enabled := False;
  Send.Enabled := False;
end;

procedure TMainUnit.OpenConnectionClick(Sender: TObject);
begin
  try
    if API= EmptyWideStr then
    begin
      ShowMessage('Register to TwelveDate and paste API key');
      Exit;
    end
    else
      FBirdSocket := TBirdSocketClient.New(API); 

    FBirdSocket.AddEventListener(TEventType.MESSAGE,
      procedure(const AText: string)
      begin
        Display(AText);
        SocketText(FBirdSocket, AText);
      end);
    FBirdSocket.Connect;
    FBirdSocket.AutoCreateHandler := True;
    DisplayMemo.Lines.Add('Websocket connection opened!');
    OpenConnection.Enabled := False;
    CloseConnection.Enabled := True;
//    FBirdSocket.HeartBeatInterval := 70;
    Timer1.Enabled := True;

    FCources(3,2,2,2);
  except
    on E:Exception do
    begin
      FBirdSocket.Disconnect;
      FreeAndNil(FBirdSocket);
      Display(E.Message);
    end;
  end
end;

procedure TMainUnit.ResetClick(Sender: TObject);
begin
  Memo1.Lines.Clear;
  Memo1.Lines.Text := '   {' +  sLineBreak +
                      '       "action": "reset"' +  sLineBreak +
                      '   }' +
                      '';
  Send.Enabled := True;
end;

procedure TMainUnit.SubscribeClick(Sender: TObject);
begin
  Memo1.Lines.Clear;
  Memo1.Lines.Text := '   {' +  sLineBreak +
                      '       "action": "subscribe",' +  sLineBreak +
                      '       "params": {' +  sLineBreak +
                      '          "symbols": "EUR/USD,BTC/USD"' +  sLineBreak +
                      '       }' +  sLineBreak +
                      '   }' +
                      '';
  Send.Enabled := True;
end;

procedure TMainUnit.UnsubscribeClick(Sender: TObject);
begin
  Memo1.Lines.Clear;
  Memo1.Lines.Text := '   {' +  sLineBreak +
                      '       "action": "unsubscribe",' +  sLineBreak +
                      '       "params": {' +  sLineBreak +
                      '          "symbols": "EUR/USD"' +  sLineBreak +
                      '       }' +  sLineBreak +
                      '   }' +
                      '';
  Send.Enabled := True;
end;

procedure TMainUnit.SendClick(Sender: TObject);
begin
  FBirdSocket.Send(Memo1.lines.Text);
  StartCources;
end;

procedure TMainUnit.SocketText(Sender: TObject; const iText: String);
begin
  var StringReader := TStringReader.Create(iText);
  var TextReader := TJsonTextReader.Create(StringReader);
  var Iterator := TJSONIterator.Create(TextReader);
  try
    Iterator.Recurse;
    while Iterator.Next do
    begin
      var cIterator := Iterator;
      if cIterator.Find('price') then
      begin
        Price.Text := cIterator.AsDouble.ToString;
      end;
    end;
  finally
    StringReader.DisposeOf;
    TextReader.DisposeOf;
    Iterator.DisposeOf;
  end;

end;

procedure TMainUnit.Timer1Timer(Sender: TObject);
begin
  FBirdSocket.Send('{"action": "heartbeat"}');
end;

end.

ПРИМЕЧАНИЕ: Я использую эту бесплатную библиотеку websocket bird-socket-client

я хочу, чтобы данные отображались следующим образом:

image.thumb.png.35243285a44e196571ab02f484f5afa1.png

12dataClient.zip

Ссылка на комментарий

Рекомендуемые сообщения

  • 0

я не загружал проект, но, раз она "зависла", то вы же должны знать, где?

и не очень понятно, в чем состоит сам ваш вопрос?

в событии, которое приходит из сокета, вы выводите пришедший текст в мемо. ок. Приходит вам чистый JSON. В чем именно состоит проблема - в парсинге JSON?
тогда к чему вся эта простыня кода, если задача - вот строка, хочу достать оттуда вот это вот значение?

Ссылка на комментарий
  • 0

А чем не устраивает пример приведенный в 

https://fire-monkey.ru/topic/853-универсальный-способ-хранения-настроек/#comment-3657

Спойлер
TSettings = class
  private 
    fname: string;
    fpass : string;
  public
    property name: string read fname write fname;
    property pass: string read fass write fpass;
end;

// вытягиваешь json-строку из файла

Settings:= TJson.JsonToObject<TSettings >( 'строка с json, которая из файла' );

// что-то поменял

s:= TJson.ObjectToJsonString( Settings );

// сохранил json-строку в файл

 

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...