thomaserlang / delphi-json

JSON parser for Delphi.
83 stars 32 forks source link

Crash on trying to parse 3 items. #6

Closed AdriaanBoshoff closed 6 years ago

AdriaanBoshoff commented 6 years ago

So it seems when I try to parse this it works fine but as soon as there are 3 players it just gives me an access violation. Any reason why?

[
  {
    "SteamID": "removed",
    "OwnerSteamID": "0",
    "DisplayName": "removed",
    "Ping": 200,
    "Address": "removed",
    "ConnectedSeconds": 6386,
    "VoiationLevel": 0.0,
    "CurrentLevel": 0.0,
    "UnspentXp": 0.0,
    "Health": 97.70907
  },
  {
    "SteamID": "removed",
    "OwnerSteamID": "0",
    "DisplayName": "removed",
    "Ping": 264,
    "Address": "removed",
    "ConnectedSeconds": 3882,
    "VoiationLevel": 0.0,
    "CurrentLevel": 0.0,
    "UnspentXp": 0.0,
    "Health": 100.0
  }
]

My parse code:

procedure Tfrmmain.GetPlayers(Data: string);
var
  users, user: TJSON;
  item: TListItem;
begin
  users := TJSON.Parse(Data);
  try
    for user in users do
    begin
      item := listViewPlayers.Items.Add;
      item.Caption := user['SteamID'].AsString;
      item.SubItems.Add(user['ConnectedSeconds'].AsString + ' Seconds');
      item.SubItems.Add(user['DisplayName'].AsString);
      item.SubItems.Add(user['Ping'].AsString);
      item.SubItems.Add(user['Address'].AsString);
      item.SubItems.Add(user['Health'].AsString);
    end;

  finally
    users.Free;
  end;
end;

EDIT: Is it possible that the string that it tries to parse is to long?

thomaserlang commented 6 years ago

I have tried to replicate your error without any success. Does the attached json fail? If not please send what fails. Also please attach where in the code the exception occurs and a stacktrace if available.

It should not be the size of the string. That's way too little text to even consider it a problem.

AdriaanBoshoff commented 6 years ago

Hi, sorry for this late reply. I had some personal stuff to deal with. I think it might be the way I'm parsing it. The actual raw json I receive is the following:

{
  "Message": "[\r\n  {\r\n    \"SteamID\": \"removed\",\r\n    \"OwnerSteamID\": \"0\",\r\n    \"DisplayName\": \"remove\",\r\n    \"Ping\": 48,\r\n    \"Address\": \"remove\",\r\n    \"ConnectedSeconds\": 5137,\r\n    \"VoiationLevel\": 0.0,\r\n    \"CurrentLevel\": 0.0,\r\n    \"UnspentXp\": 0.0,\r\n    \"Health\": 100.0\r\n  },\r\n  {\r\n    \"SteamID\": \"remove\",\r\n    \"OwnerSteamID\": \"0\",\r\n    \"DisplayName\": \"removed\",\r\n    \"Ping\": 51,\r\n    \"Address\": \"removed\",\r\n    \"ConnectedSeconds\": 2158,\r\n    \"VoiationLevel\": 0.0,\r\n    \"CurrentLevel\": 0.0,\r\n    \"UnspentXp\": 0.0,\r\n    \"Health\": 95.87633\r\n  }\r\n]",
  "Identifier": 0,
  "Type": "Generic",
  "Stacktrace": ""
}

So what I do is I parse it and then parse the "Message" giving me the following:

[
  {
    "SteamID": "removed",
    "OwnerSteamID": "0",
    "DisplayName": "removed",
    "Ping": 200,
    "Address": "removed",
    "ConnectedSeconds": 6386,
    "VoiationLevel": 0.0,
    "CurrentLevel": 0.0,
    "UnspentXp": 0.0,
    "Health": 97.70907
  },
  {
    "SteamID": "removed",
    "OwnerSteamID": "0",
    "DisplayName": "removed",
    "Ping": 264,
    "Address": "removed",
    "ConnectedSeconds": 3882,
    "VoiationLevel": 0.0,
    "CurrentLevel": 0.0,
    "UnspentXp": 0.0,
    "Health": 100.0
  }
]

How would you recommend I parse the raw data I receive from the server so that I can get a proper player list.

How the code goes:

Receive data from server:

procedure Tfrmmain.websocketclientWebRconDataIn(Sender: TObject; DataFormat: Integer; Text: string; EOM: Boolean);
begin
  LogWebRcon(ParseWebRconIncomingData(Text));
end;

Parse the data:

function Tfrmmain.ParseWebRconIncomingData(Data: string): string;
var
  Text: TJSON;
  chat2: TJSON;
  chat: string;
begin
  Text := TJSON.Parse(Data);
  try
    if (Text['Type'].AsString = 'Generic') or (Text['Type'].AsString = 'Error') or (Text['Type'].AsString = 'Warning') then
    begin
      if Text['Identifier'].AsInteger = 10 then    //This will run if the Identidier is 10 because this means the playerlist has been requested
      begin
        GetPlayers(Text['Message'].AsString);
      end
      else
        Result := Text['Message'].AsString;
      Text.Free;
    end
    else if Text['Type'].AsString = 'Chat' then
    begin
      chat := Text['Message'].AsString;
      chat2 := TJSON.Parse(chat);
      Result := chat2['Username'].AsString + ': ' + chat2['Message'].AsString;
      chat2.Free;
    end;
  except
    on E: Exception do
      ShowMessage(E.Message);
  end;

end;

Get Playerlist:

procedure Tfrmmain.GetPlayers(Data: string);
var
  users, user: TJSON;
  item: TListItem;
begin
  users := TJSON.Parse(Data);
  try
    try
      for user in users do
      begin
        item := listViewPlayers.Items.Add;
        item.Caption := user['SteamID'].AsString;
        item.SubItems.Add(user['ConnectedSeconds'].AsString + ' Seconds');
        item.SubItems.Add(user['DisplayName'].AsString);
        item.SubItems.Add(user['Ping'].AsString);
        item.SubItems.Add(user['Address'].AsString);
        item.SubItems.Add(user['Health'].AsString);
      end;
    finally
      users.Free;
    end;
  except
    on E: Exception do
      ShowMessage(E.Message);
  end;

end;

Logging:

procedure Tfrmmain.LogWebRcon(Text: string);
begin
  mmoWebRconLog.Lines.Add('[' + DateTimeToStr(now) + '] ' + Text);
end;

I understand this code is really messy but this was the only way I could get it working. BTW there is no break or anything. My program just crashes even if I run it in debug mode.

EDIT:

So I've recreated the code so that everything happens in one function except for the logging part.

function Tfrmmain.ParseWebRconIncomingData(Data: string): string;
var
  Text: TJSON;
  chat: TJSON;
  users, user: TJSON;
  item: TListItem;
begin
  Text := TJSON.Parse(Data);
  if Text['Identifier'].AsInteger = 1 then
  begin
    //GetPlayers(Text['Message'].AsString);
    users := TJSON.Parse(Text['Message'].AsString);
    for user in users do
    begin
      try
        item := listViewPlayers.Items.Add;
        item.Caption := user['SteamID'].AsString;
        item.SubItems.Add(user['ConnectedSeconds'].AsString + ' Seconds');
        item.SubItems.Add(user['DisplayName'].AsString);
        item.SubItems.Add(user['Ping'].AsString);
        item.SubItems.Add(user['Address'].AsString);
        item.SubItems.Add(user['Health'].AsString);
      except
        on E: Exception do
          ShowMessage(e.Message);
      end;
    end;
    Result := '';
    Exit;
  end
  else if (Text['Type'].AsString = 'Generic') or (Text['Type'].AsString = 'Error') or (Text['Type'].AsString = 'Warning') then
  begin
    Result := Text['Message'].AsString;
    Exit;
  end
  else if Text['Type'].AsString = 'Chat' then
  begin
    chat := TJSON.Parse(Text['Message'].AsString);
    Result := chat['Message'].AsString;
  end;
end;
AdriaanBoshoff commented 6 years ago

Here is a msg fromt he debugger: https://i.imgur.com/KlkhpyZ.png and https://i.imgur.com/A7OXZHV.png

AdriaanBoshoff commented 6 years ago

It seems to only happen after it runs this: item.SubItems.Add(user['Health'].AsString);

thomaserlang commented 6 years ago

So this is interesting. Your code works on my machine. Have you checked that you run the latest version of djson.pas?

I would recommend that you install madexcept. It'll give you a usable stacktrace, making it easier to debug these access violations.

AdriaanBoshoff commented 6 years ago

Thanks will do. I have a really big project that i started back in mod 2017. Started with rad studio 10.1.1 and now the latest. I've had a huge amount of issues lately. I'm thinking of just starting the project over amd copy / pasting the code.