2

Good evening all.

I'm currently developing a cross-platform compatible version of my product WinFlare. The issue I'm facing is that SuperObject still isn't cross-platform compatible with Firemonkey. By all means, I used it in the original version of the product, but now I want to create a cross-platform version as opposed to one limited to just Windows, I'm finding it to be a hassle.

DBXJSON is the only cross-platform solution I've been able to find after extensive hours of research, but that's proving to be frustrating to try and deal with. Most all of the examples I've found for it either don't apply for my situation, or they're too complicated to gleam anything useful from. There's lots of discussion, but I'm just struggling to get to grips with what was such a simple task with SuperObject. I've spent the best part of this evening trying to find something that works to build from, but everything I've tried has just led me back to square one.

Ideally, I'd like to fix up SuperObject, but I lack the knowledge to go so in depth as to make it cross-platform compatible with OS X (and ready for the mobile studio). I'd welcome any suggestions on that, but as I imagine no one's got the time to go through such a huge task, it looks like DBXJSON is my only option.

The JSON layout I'm dealing with is still the same;

{
  response: {
    ips: [
       {
         ip: "xxx.xxx.xxx.xxx",
         classification: "threat",
         hits: xx,
         latitude: xx,
         longitude: xx,
         zone_name: "domain-example1"
         },
        {
         ip: "yyy.yyy.yyy.yyy",
         classification: "robot",
         hits: yy,
         latitude: xx,
         longitude: xx,
         zone_name: "domain-example2"
         }
       ]
   }
  result : "success",
  msg: null
}

There can be hundreds of results in the ips array. Let's say I want to parse through all of the items in the array and extract every latitude value. Let's also assume for a second, I'm intending to output them to an array. Here's the sort of code template I'd like to use;

procedure ParseJsonArray_Latitude(SInput : String);
var
  i : Integer;
  JsonArray : TJsonArray;
Begin
  // SInput is the retrieved JSON in string format
  { Extract Objects from array }

  for i := 0 to JsonArray.Size-1 do
  begin
    Array_Latitude[i] := JsonArray.Item[i].ToString;
  end;
end;

Essentially, where it says { Extract Objects from array }, I'd like the most basic solution using DBXJSON that would solve my problem. Obviously, the calls I've shown related to JsonArray in the template above might not be correct - they're merely there to serve as an aid.

Scott P
  • 1,462
  • 1
  • 19
  • 31
  • SuperObject is just text processing. I can't see how it would not work with all platforms – David Heffernan Feb 04 '13 at 01:02
  • SuperObject uses `Windows` and `WinSock` units, along with a great number of procedures making use of Windows-only routines. It does have some IFDEFS for FPC and UNIX, but none for MACOS. It's pretty deeply embedded in the code. – Scott P Feb 04 '13 at 02:04
  • Fair enough. Seems like a poor do though. – David Heffernan Feb 04 '13 at 02:08
  • Have a look at http://code.google.com/p/dwscript/source/browse/ the JSON unit can be used "alone". –  Feb 04 '13 at 09:23
  • I did look into dwscript, but as with SuperObject, `dwsJSON.pas` also brings in some other units that aren't crossplatform compatible with OS X (`dwsXPlatform` for example). – Scott P Feb 04 '13 at 15:38

3 Answers3

3

First, parse the string to get an object.

var
  obj: TJsonObject;

obj := TJsonObject.ParseJsonValue(SInput) as TJsonObject;

That gives you an object with three attributes, response, result, and msg. Although ParseJsonValue is a method of TJsonObject, and your particular string input happens to represent an object value, it can return instances of any TJsonValue descendant depending on what JSON text it's given. Knowing that's where to start is probably the hardest part of working with DbxJson.

Next, get the response attribute value.

response := obj.Get('response').JsonValue as TJsonObject;

That result should be another object, this time with one attribute, ips. Get that attribute, which should have an array for a value.

ips := response.Get('ips').JsonValue as TJsonArray;

Finally, you can get the values from the array. It looks like you're expecting the values to be numbers, so you can cast them that way.

for i := 0 to Pred(ips.Size) do
  Array_Latitude[i] := (ips.Get(i) as TJsonObject).Get('latitude').JsonValue as TJsonNumber;

Remember to free obj, but not the other variables mentioned here, when you're finished.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • Still having a few problems with this. `TJsonValue` doesn't have a `.get` routine (I also tried with `TJsonObject` but that causes problems later in the code). I'm also still confused about what types `response` and `ids` are (sidenote `ids` should probably read `ips` as reference to the JSON in the question). I managed to build a solution that's 35 lines long - albeit without it outputting to a dynamic array (instead outputs straight to a memo), but your solution implies it should be much shorter. – Scott P Feb 04 '13 at 17:38
  • I've fixed the ids/ips mistake, and made it clear what the variables' types are, based on what type they're cast to during assignment. I can't help with the other problems you mention due to lack of details. What are you doing that you don't think you should have to do? – Rob Kennedy Feb 04 '13 at 17:51
  • Thanks for that Rob. I'm marking your answer as the solution as it brought me close enough to be able to figure it out from there (once the types had been clarified). I'll be posting my solution as an answer shortly. – Scott P Feb 04 '13 at 19:05
2

For completion, since the question stated that there was no alternative to DBXJSON for cross-platform, I would like to point out two Open Source alternatives, which appeared since the initial question.

SynCrossPlatformJSON is able to create schema-less objects or arrays, serialize and unserialize them as JSON, via a custom variant type, including late-binding to access the properties.

For your problem, you could write:

var doc: variant;
    ips: PJSONVariantData; // direct access to the array
    i: integer;
...
  doc := JSONVariant(SInput);   // parse JSON Input and fill doc custom variant type
  if doc.response.result='Success' then       // easy late-binding access
  begin
    ips := JSONVariantData(doc.response.ips); // late-binding access into array
    SetLength(Arr_Lat,ips.Count);
    for i := 0 to ips.Count-1 do begin
      Arr_lat[i] := ips.Values[i].latitude;
      Memo1.Lines.add(ips.Values[i].latitude); 
     end;
  end;  
... // (nothing to free, since we are using variants for storage)

Late-binding and variant storage allow pretty readable code.

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • The question clearly asks for a solution using DBXJSON: "I'd like the most basic solution **using DBXJSON** that would solve my problem". I'm not sure how posting an advert for your unit and source that uses it is an answer to that question. Can you clarify how this solves the problem **using DBXJSON**? – Ken White May 19 '14 at 20:53
  • Because he was not able to find alternatives. And there are at least xsuperobject and our unit. – Arnaud Bouchez May 19 '14 at 21:42
  • Then you've simply decided to ignore the specific question asked, which was how to do this **using DBXJSON**? A question asking for a tool/library recommendation would be off-topic, but this question doesn't do so - it's quite clear about the tool being asked about here. "How do I fix my Porsche? Well, if you had a BMW you could..." is not a suitable answer. – Ken White May 19 '14 at 21:46
  • 1
    Please read again the beginning of the question to find out the context. – Arnaud Bouchez May 19 '14 at 21:51
  • I got the context, thanks. Please read again the actual question being asked, which I've referred to now three times - it's in the sentence I quoted in my first comment to your answer. I'll provide it again: "I'd like the most basic solution **using DBXJSON** that would solve my problem." You have not addressed that question at all, even remotely. – Ken White May 19 '14 at 21:54
  • 3
    @KenWhite quote: `I'd welcome any suggestions on that, but (...) it looks like DBXJSON is my only option.` - now there is at least one option without DBXJSON, so it should be welcomed – mjn May 20 '14 at 06:32
0

Thanks to assistance from Rob Kennedy, I managed to build a solution that solved the problem;

var
  obj, response, arrayobj : TJSONObject;
  ips : TJSONArray;
  JResult : TJsonValue;
  i : Integer;
  Arr_Lat : Array of string;
begin
try
  Memo1.Lines.Clear;
  obj := TJsonObject.ParseJSONValue(SInput) as TJSONObject;
  response := Obj.Get('response').JsonValue as TJSONObject;
  ips := response.Get('ips').JsonValue as TJSONArray;
  SetLength(Arr_Lat, ips.Size-1);
  for i := 0 to ips.Size-1 do
    begin
      arrayobj := ips.Get(i) as TJSONObject;
      JResult := arrayobj.Get('latitude').JsonValue;
      Arr_lat[i] := JResult.Value;
      Memo1.Lines.Add(JResult.Value);
    end;
finally
  obj.Free;
end;

This will add the results to both the array (Arr_Lat), and output them to the memo (Memo1).

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