I'm thinking about implementing JSON parser for Delphi. What should a good JSON parser do? Any ideas for requirements? I believe it should at least output and process JSON... Looking at XML parsers, should it be more DOM or SAX-like?
A good floor would be all the functionality provided by the following 3 (taken from JSON.org): uJson, JSON Toolkit, and lkjson.
OK. These are all three JSON parsers listed on JSON.org. I'm not happy with none of them. Let's forget about Delphi for a sec. I'm looking for general requirements for a JSON parser.
Well, the nice thing about JSON is that it uses a fairly simple grammar and writing a parser for it is fairly simple. "forgetting" all of the other implementations, but using Delphi I would start with the TParser class in the classes unit for a jumpstart. Create separate methods to handle each element (some are already done in TParser, which is why I suggested starting from there).
Now, what to do with what you have parsed. There is where the fun comes in. If you mimic the interfaces and implementation of the TXmlDocument, then conversion to/from XML/JSON is somewhat trivial.
I've used the JSON toolkit in several projects, with great success. The only thing I've modified at some point was the way it formats resulting JSON, but it's a matter of personal taste.
It's free, fairly clean, and easy to use. No need to install packages; just have a .pas file somewhere in your path. Just check test_usage.dpr below for some simple examples on how to use it. It doesn't get much easier than that.
I wouldn't waste my time trying to implement yet another JSON parser, unless you want to do it for educational purposes, in which case you should carefully study existing implementations anyway.
JSON Toolkit home: http://www.progdigy.com/?page_id=6
program test_usage;
{$IFDEF FPC}
{$MODE OBJFPC}{$H+}
{$ELSE}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
SysUtils,
superobject;
var
my_string, my_int, my_object, my_array: ISuperObject;
new_obj: ISuperObject;
j: integer;
ite: TSuperObjectIter;
begin
try
my_string := TSuperObject.Create(#9);
writeln('my_string=', my_string.AsString);
writeln('my_string.AsJSon=', my_string.AsJSon);
my_string := TSuperObject.Create('foo');
writeln('my_string=', my_string.AsString);
writeln('my_string.AsJson=', my_string.AsJson);
my_int := TSuperObject.Create(9);
writeln('my_int=', my_int.AsInteger);
writeln('my_int.AsJson=', my_int.AsJson);
my_array := TSuperObject.Create(stArray);
my_array.I[''] := 1; // append
my_array.I[''] := 2; // append
my_array.I[''] := 3; // append
my_array.I['4'] := 5;
writeln('my_array=');
with my_array.AsArray do
for j := 0 to Length - 1 do
if O[j] = nil then
writeln(#9'[', j,']=', 'null') else
writeln(#9'[', j,']=', O[j].AsJson);
writeln('my_array.AsJson=', my_array.AsJson);
my_object := TSuperObject.Create(stObject);
my_object.I['abc'] := 12;
// my_object.S['path.to.foo[5]'] := 'bar';
my_object.B['bool0'] := false;
my_object.B['bool1'] := true;
my_object.S['baz'] := 'bang';
my_object.S['baz'] := 'fark';
my_object.AsObject.Delete('baz');
my_object['arr'] := my_array;
writeln('my_object=');
if ObjectFindFirst(my_object, ite) then
repeat
writeln(#9,ite.key,': ', ite.val.AsJson);
until not ObjectFindNext(ite);
ObjectFindClose(ite);
writeln('my_object.AsJson=', my_object.AsJson);
new_obj := TSuperObject.Parse('"003"');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('/* hello */"foo"');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('// hello'#10'"foo"');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('"\u0041\u0042\u0043"');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('null');
if new_obj = nil then
writeln('new_obj.AsJson=', 'null');
new_obj := TSuperObject.Parse('true');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('12');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('12.3');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('["\n"]');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('["\nabc\n"]');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('[null]');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('[]');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('["abc",null,"def",12]');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('{}');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('{ "foo": "bar" }');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('{ "foo": "bar", "baz": null, "bool0": true }');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('{ "foo": [null, "foo"] }');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('{ "abc": 12, "foo": "bar", "bool0": false, "bool1": true, "arr": [ 1, 2, 3, null, 5 ] }');
writeln('new_obj.AsJson=', new_obj.AsJson);
new_obj := TSuperObject.Parse('{ foo }');
if (new_obj = nil) then
writeln('got error as expected');
my_string := nil;
my_int := nil;
my_object := nil;
my_array := nil;
new_obj := nil;
writeln(#10'press enter ...');
readln;
except
on E: Exception do
writeln(E.Message)
end;
end.
I think Json.NET does a pretty bang up job.
- JsonReader and JsonWriter for low level work of parsing and outputting JSON
- JsonSerializer for converting objects to and from JSON
- JObject, JArray and JValue classes for working with JSON in an object model
Note I may be a bit bias since I wrote it :)
I agree with James; there are 3 sensible ways to work with Json: as a stream of events/tokens; as a tree (like XML DOM), or by binding to/from "native" objects. The package I am familiar with is Jackson (http://jackson.codehaus.org), and it also supports these 3 methods, similar to how (I assume) Json.NET does.
I have implemented a pull-style parser in Java, which I find very nice to use. It parses strictly conforming JSON, with some relaxations accepted (primarily for my specific purposes). It is published and detailed on my website. Also published is an additional method which illustrates a document load using the parser - so you can use it either stream-oriented or document oriented.
I highly recommend pull style parsng (I have an XML parser which is pull, also).