2

I have a very weird situation.

This is the JSON I am trying to parse:

[
  {
    "username":"xxx",
    "email":"[email protected]",
    "custom_title":"xxx title",
    "timezone":"Africa\/Cairo",
    "message_count":"218",
    "alerts_unread":"0",
    "like_count":"385",
    "friend_count":"0"
  }
]

This is my parsing code:

type
  TUserData = record
    email, timezone: string;
    msg, alerts, likes: integer;
 end;

procedure TDMRest.parseData(var b: TUserData);
var
  jtr: TJsonTextReader;
  sr: TStringReader;
begin
  //RESTResponseLogin.Content has the above json text
  sr := TStringReader.Create(RESTResponseLogin.Content);
  try
    jtr := TJsonTextReader.Create(sr);
    try
      while jtr.Read do
      begin
        if jtr.TokenType = TJsonToken.StartObject then
          process(b, jtr);
      end;
    finally
      jtr.Free;
    end;
  finally
    sr.Free;
   end;
end;

//here there is a problem
procedure TDMRest.process(var c: TUserData; jtr: TJsonTextReader);
begin
  while jtr.Read do
  begin
    if (jtr.TokenType = TJsonToken.PropertyName) then
    begin
      if jtr.Value.ToString = 'email' then
      begin
        jtr.Read;
        c.email := jtr.Value.AsString;
      end;

      if jtr.Value.ToString = 'timezone' then
      begin
        jtr.Read;
        c.timezone := jtr.Value.AsString;
      end;

      if jtr.Value.ToString = 'message_count' then
      begin
        jtr.Read;
        c.msg := jtr.Value.AsInteger;
      end;

      if jtr.TokenType = TJsonToken.EndObject then
      begin
        c.alerts := 0;
        c.likes := 0;
        exit;
      end;
    end;
  end;
end;

MY PROBLEM: In the process() code, the first 2 if blocks (email and timezone) can read values into my record. But when I add other if blocks (like if jtr.Value.ToString = 'message_count' then), I cannot see the values of my record anymore.

Am I parsing the data properly?

Basically, I need to grab the info from a JSON string and put the data inside a TUserData record.

I have found the above pattern in a book titled "Expert Delphi", and I am pretty sure that the parseData() function is correct. Probably I am missing something in the process.

The TDMRrst is a DataModule; I am giving the function a record, and I'd like the data to be properly parsed.

What is wrong here?

5
  • 1
    You're reading message_count AsInteger whereas, according to your sample, message_count is a string - which raises an exception. Surely you were seeing that exception being raised? Commented Sep 14, 2017 at 23:56
  • @Jason so should it be c.msg := StrToInt(jtr.Value.AsString);? Commented Sep 15, 2017 at 0:02
  • I thought that AsInteger converted my string value into an integer! Commented Sep 15, 2017 at 0:03
  • Yes, that would work - assuming you have no control over the sending content Commented Sep 15, 2017 at 0:09
  • @RaffaeleRossi: jtr.Value is a TValue, whose AsInteger() method DOES NOT convert a string value to an integer value. TValue is designed to mimic the implicit conversions of the Pascal language, and you can't assign a string directly to an integer. You are probably thinking of Variant, which CAN do that kind of conversion. Commented Sep 15, 2017 at 0:15

1 Answer 1

3

In the JSON you have shown, all of the values are strings, there are no integers. So, when you call jtr.Value.AsInteger for the message_count value, it raises a conversion exception that you are not catching. TValue.AsInteger DOES NOT perform an implicit conversion from string to integer for you.

You will have to use jtr.Value.AsString instead and convert the string to an integer using StrToInt():

if jtr.Value.ToString = 'message_count' then
begin
  jtr.Read;
  //c.msg := jtr.Value.AsInteger;
  c.msg := StrToInt(jtr.Value.AsString);
end;

Do the same for the other "integer" values in the JSON (alerts_unread, like_count, and friend_count).

Sign up to request clarification or add additional context in comments.

7 Comments

As far as I remember, Delphi has some kind of JSON to Delphi (and vice versa) object value assignments. You just define object and the rest is done by RTTI and a single method call. Marshaller and unmarshaller.
It seems to be TJSONUnMarshal.CreateObject from REST.JsonReflect unit (OP uses REST). You just create TJSONUnMarshal instance and pass to the CreateObject method desired class type and a JSON object. And if you're lucky enough, you'll get an object instance with no manual parsing. Of course the output structure must be object, not record as records have no RTTI information.
@Victoria: That appraoch is documented on Embarcadero's DocWiki: Serializing User Objects. But, does that implicitly convert string values to integer variables? If not, then you will have to define a custom convertor for that operation, or else define the marshaled class to use String for its integer values.
I was hoping in an attribute by which you say how to convert an input JSON string value to an integer field. Cannot find any :(
@Victoria: maybe something wiith the JSONReflect attribute? I don't use JSON marshaling.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.