4

I recently came up against a wall when parsing JSON. You see, when working with the CloudFlare Client Interface API, i wanted to lookup the "threat rating" for a specific IP. The issue is that due to the design of the API, the format is something like this;

{
 response: {
            xxx.xxx.xxx.xxx: <value>,
            calls_left: 299
            },
 result: "success",
 msg: null
}

xxx.xxx.xxx.xxx represents the field name i needed to retrieve data from. Immediately, you can probably see the issue i was facing; a dot character in a parse string is assumed to be a sublevel in the current path.

<value> represents the actual rating of the IP. However, the format, and data type returned from it, varies. On IP's that aren't a threat, or don't have a threat rating, it returns false as a boolean. On Search Engine crawlers, it returns "SE:<var>" (where <var> is a numerical value) as a string. On known threats, it returns "BAD:<var>" (where <var> is a numerical value). As such, i couldn't rely on a known data type being returned.

The main issue however, is that attempting to read the value from this field would obviously fail due to the dots within the field name.

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
Scott P
  • 1,462
  • 1
  • 19
  • 31

2 Answers2

7

No need for hacks. All you need is to go through AsObject. The name, including embedded dots, is then used without treating it as a path specifier.

const Line1 = '{'
      + #13#10' response: {'
      + #13#10'            xxx.xxx.xxx.xxx: "somestring",'
      + #13#10'            calls_left: "299"'
      + #13#10'            },'
      + #13#10' result: "success",'
      + #13#10' msg: null'
      + #13#10'}';

var
  super: ISuperObject;
  res: ISuperObject;
begin
  super := SO(Line1);
  res := super.O['response'];

  writeln('S[calls_left''] on res: ', res.S['calls_left']);
  writeln('S[''xxx.xxx.xxx.xxx''] on res: ', res.S['xxx.xxx.xxx.xxx']);

  writeln('Names: ', res.AsObject.GetNames.AsString);
  writeln('Values: ', res.AsObject.GetValues.AsString);

  writeln('S[''xxx.xxx.xxx.xxx''] on res: through AsObject: ',
    res.AsObject.S['xxx.xxx.xxx.xxx']);

Gives the following results:

S[calls_left'] on res: 299
S['xxx.xxx.xxx.xxx'] on res:
Names: ["calls_left","xxx.xxx.xxx.xxx"]
Values: ["299","somestring"]
S['xxx.xxx.xxx.xxx'] on res: through AsObject: somestring
Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
1

The trick to this is somewhat of a crude workaround of this "limitation". There may be other ways -- There may even be a function within SuperObject to achieve this -- but this was the one that most newer coders would perhaps be comfortable with (either new to Delphi, New to Coding, or new to SuperObject). It utilizes the StringReplace function to replace "." instances with a more fieldname-friendly character (in this case, i use *). As i said, it's a crude method, and there may be an easier way, but a simple solution that newer coders can learn from has it's place.

Details

Please note that this specific example is for the current CloudFlare API, and as such, would be advised to expand from. It requires SuperObject to work.

MyJSON represents the already retrieved JSON as a string. This can be TMemo.text, TEdit.text, or just a standard string.

MyIP represents the IP you wish to look for.

Function GetValue(MyJson, MyIP : String) : String;
var
  ISO : ISuperObject;
  FmIP, FmJSON : String;
begin  
  FmJSON := StringReplace(MyJSON,'.','*',[rfReplaceAll]);
  FmIP := StringReplace(MyIP,'.','*',[rfReplaceAll]);
  ISO := SO(FmJSON);
  Result := iso.s['response'+'.'+FmIP];
end;

This will return the result as a string regardless of whether it's a Boolean, Integer or String in the JSON. As you can see, it's extremely easy to understand while achieving the goal perfectly. The potential issue is if a result has a . in it then your output value will have a * in it's place. You could quite easily wrap the result with StringReplace, something like this;

StringReplace(iso.s['response'+'.'+FmIP],'*','.',[rfReplaceAll]);

Expanding from this

You could potentially expand from this by using Pos or AnsiPos to find the position of the IP itself. Expanding from that, it's relatively easy to get the position of the final character and as such, you could find a function that can be used outside of this specific situation. However, discussing that would be better served as a general "Useful String Functions" answer, and not for this specific discussion.

Any other answers are welcome of course!

Scott P
  • 1,462
  • 1
  • 19
  • 31