JSON support has been introduced in Delphi 2010 as a part of DBExpress database driver architecture, but of course JSON support is not limited to just database applications. JSON is similar to XML as both are text-based data interchange formats. Delphi 6 introduced “TXMLDocument” component and the “XML Data Binding Wizard” to make it easier to work with XML documents in code. In this paper I’m presenting a similar component for JSON called “TJSONDocument”. The next step was to implement “TJSONTreeView” component for displaying the content of a ”TJSONDocument” in VCL Forms applications. Using these components a simple JSON Viewer application has been created and described here. In the XML world there are two categories of parsers: DOM and SAX. DOM parsers takes XML string and builds its in-memory representation – a “Document Object Model” - for application to traverse and update it. At the other hand SAX is a streaming interface — applications receive information from XML documents in a continuous stream, with no backtracking or navigation allowed. In the later part of this article I’m describing the “TJSONParser” component that was created as an experimental TJSONDocument-descendant that provides SAX-like event-based processing for JSON. The full source code described in this paper can be downloaded from [1, 2] https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemo s/branches/RadStudio_XE/Delphi/DataSnap/JSONViewer See “References” section at the end of this article.
Why JSON? JSON is relatively new as it was first described by Douglas Crockford in July 2006 in his IETF Request for Comments “The application/json Media Type for JavaScript Object Notation”[2]. In many respects JSON is similar to XML as both are text based data interchange formats used broadly in the Web. While XML has now became the whole family of related standards – including XML Namespaces, XML Schema, XSL, XPath and others – JSON defines only a small set of formatting rules for the portable representation of structured data. The key strength of JSON is simplicity. Douglas Crockford describes JSON structure in his paper presented at XML 2006 Conference in Boston “JSON: The Fat-Free Alternative to XML” [3]: The types represented in JSON are strings, numbers, booleans, object, arrays, and null. JSON syntax is nicely expressed in railroad diagrams. Hide
image
JSON only has three simple types – strings, numbers and Booleans – and two complex types – arrays and objects. A string is a sequence of zero or more characters wrapped in quotes with backslash escapement, the same notation used in most programming languages. A number can be represented as integer, real, or floating point. JSON does not support octal or hex. It does not have values for NaN or Infinity. Numbers are not quoted. A JSON object is an unordered collection of key/value pairs. The keys are strings and the values are any of the JSON types. A colon separates the keys from the values, and comma separates the pairs. The whole thing is wrapped in curly braces. The JSON array is an ordered collection of values separated by commas and enclosed in square brackets. The character encoding of JSON text is always Unicode. UTF-8 is the only encoding that makes sense on the wire, but UTF-16 and UTF-32 are also permitted. JSON has no version number. No revisions to the JSON grammar are anticipated. JSON has become the X in Ajax. It is now the preferred data format for Ajax applications. The most common way to use JSON is with XMLHttpRequest. Once a response text is obtained, it can quickly be converted into a JavaScript data structure and consumed by a program JSON's syntax is significantly simpler than XML, so parsing is more efficient. JSON doesn't have namespaces. Every object is a namespace: its set of keys is independent of all other objects, even exclusive of nesting. JSON uses context to avoid ambiguity, just as programming languages do. JSON has no validator. Being well-formed and valid is not the same as being correct and relevant. Ultimately, every application is responsible for validating its inputs. Below is a fragment of sample JSON text, based on “Sample Confabulator Widget” from [4], used
in the later part of this article. { "widget": { "debug": "on", "window": { "title": "Sample Konfabulator Widget", "name": "main_window", "width": 500, "height": 500 }, "misc": ["hello", 23, false] } }
Delphi and DBXJSON.pas There is a big number of programming languages that have built-in support for JSON or libraries to work with JSON. These JSON bindings for different programming languages are listed on JSON homepage [4], including three open source Delphi libraries. Since Delphi 2010 the JSON support is part of the VCL library as implemented in the DBXJSON.pas unit. In order to visualize Delphi classes responsible for JSON support, I have added the “DBXJSON” unit directly to a little test Delphi application, clicked on “Model Support” tab in Project Manager and got the following UML class diagram. Some of the classes from DBXJSON.pas unit not related directly to JSON support are not shown here. Hide
image
In Delphi 2007 the DBX database driver framework architecture has been reengineered in pure Delphi code and introduced a number of interesting features including extensible command types. In the next release – Delphi 2009 – the DBX architecture has been extended and DataSnap framework for building client/server and multi-tier application has been reengineered as well as the extension of the new DBX architecture. One of the most interesting and powerful capabilities introduced to DataSnap support in the following Delphi release – Delphi 2010 – were lightweight callbacks passed to DataSnap server methods for the server application to be able to call back into
the client. The DBXJSON.pas unit defines the abstract base class for callback objects that contains “Execute” method that accepts and returns parameters of TJSONValue type, thus making it possible to pass in and out arbitrarily complex data structures encoded as JSON. Here is the declaration of this class: TDBXCallback = class abstract public function Execute(const Arg: TJSONValue): TJSONValue; virtual; abstract; // … other members stripped out end; The DBXJSON unit contains also functionality to parse JSON text into the graph of TJSONValuedescedants and to generate JSON text from the graph of objects in memory. On the graphics “TJSONAncestor.Owned: Boolean” property has been expanded to underline the fact that all JSON descendants have the “Owned” property that controls the lifetime of JSON objects in memory. TJSONObject class contains static method “ParseJSONValue” that effectively implements JSON parser functionality. It accepts a string parameter with JSON text and returns a TJSONValue reference to the root of the graph of TJSONAncestor-descendants. It is also possible to generate JSON text from the in-memory tree of JSON objects calling “ToString” overloaded method on any of “TJSONAncestor” descendants.
TJSONDocument Component Before Delphi 2010 introduced DBXJSON unit, I was trying to implement JSON parsing functionality manually coding JSON railroad diagrams. With DBXJSON implementation in place there is little point in reinventing the wheel; however there is still no design-time support for JSON. Everything has to be done in code. Hence the concept of creating a minimal VCL component
wrapper
for
JSON
parser
implementation
provided
by
“TJSONObject.ParseJSONValue” class method that accepts JSON text and returns the object tree representing JSON document structure in memory. “TJSONDocument” component has been implemented inside “jsondoc” unit to keep some level of parity with its “TXMLDocument” component equivalent that is implemented inside “xmldoc” unit. Below is the declaration of the “TJSONDocument” VCL component: unit jsondoc; //… type TJSONDocument = class(TComponent)
private FRootValue: TJSONValue; FJsonText: string; FOnChange: TNotifyEvent; procedure SetJsonText(const Value: string); procedure SetRootValue(const Value: TJSONValue); protected procedure FreeRootValue; procedure DoOnChange; virtual; public class function IsSimpleJsonValue(v: TJSONValue): boolean; inline; class function UnQuote(s: string): string; inline; class function StripNonJson(s: string): string; inline; constructor Create(AOwner: TComponent); override; destructor Destroy; override; function ProcessJsonText: boolean; function IsActive: boolean; function EstimatedByteSize: integer; property RootValue: TJSONValue read FRootValue write SetRootValue; published property JsonText: string read FJsonText write SetJsonText; property OnChange: TNotifyEvent read FOnChange write FOnChange; end; The full source code of this component and all other source code described in this paper can be downloaded from [1]. See “References” section at the end of this article. The “TJSONDocument” contains published “JsonText: string” property that can be used to assign JSON text for parsing and “RootValue: TJSONValue” public property that can be used to assign “TJSONValue” reference and generate JSON text. Assigning to any of these properties cause the other property to be updated and the “OnChange” event is fired every time the JSON stored inside the component is changed. In this way it is possible for other components of an application to be notified and refreshed. In this sense “TJSONDocument” component can be used as a JSON parser and generator as described in the original JSON RFC [2]. The public “IsActive: boolean” property returns true if TJSONDocument component contains valid JSON, or false it is empty. function TJSONDocument.IsActive: boolean; begin Result := RootValue <> nil; end;
The “TJSONObject.ParseJSONValue: TJSONValue” method is sensitive to the contents of the JSON text passed for parsing. If the string provided does not contain valid JSON text or it contains JSON text with additional whitespace characters, than it is always returning “nil” TJSONValue reference. The class function “StripNonJson” is used to remove from JSON text any non JSON characters and is implemented as follows using “TCharacter” class from the VCL “Character” unit. class function TJSONDocument.StripNonJson(s: string): string; var ch: char; inString: boolean; begin Result := ''; inString := false; for ch in s do begin if ch = '"' then inString := not inString; if TCharacter.IsWhiteSpace(ch) and not inString then continue; Result := Result + ch; end; end; The process of JSON parsing is implemented in “ProcessJsonText” method that is called as a sideeffect of assigning to “JsonText: string” published property. procedure TJSONDocument.SetJsonText(const Value: string); begin if FJsonText <> Value then begin FreeRootValue; FJsonText := Value; if FJsonText <> '' then ProcessJsonText end; end; function TJSONDocument.ProcessJsonText: boolean; var s: string; begin FreeRootValue; s := StripNonJson(JsonText); FRootValue := TJSONObject.ParseJSONValue(BytesOf(s),0);
Result := IsActive; DoOnChange; end; The “TJSONDocument” was designed to be as minimal as possible. For convenience it also surfaces “EstimatedByteSize: integer” method provided by the underlying DBXJSON implementation. This is how the “TJSONDocument” component looks at design-time inside the Delphi 2010 Object Inspector. Hide
image
TJSONTreeView Component I have always wanted to implement a JSON viewer in DelphiJ The “TJSONDocument” component is non-visual, so I needed a separate visual component that would provide graphical, tree-representation of JSON. This component should have a “JSONDocument” published property to connect both components at design-time. How JSON should be visualized? Is simple Delphi “TTreeView” component good enough or maybe I should do some fancy painting in code? Maybe I should use TVirtualTreeView component to have a tree with multiple columns? These are all good questions, so I had to do a little googling around for inspiration. There are simpler and more complex JSON viewers available in Internet. Some of them are standalone applications,
like
the
one
coded
in
.NET
and
available
at
http://jsonviewer.codeplex.com/. Other viewers are embedded at web pages likehttp://www.jsonviewer.com/ or http://jsonviewer.stack.hu/. The one that I liked the most was implemented in Java and is available as a part of the Apache Pivot project for Rich Internet Applications (http://pivot.apache.org/demos/jsonviewer.html). The one cosmetic thing that I do not like about it, is that is sorts JSON properties alphabetically and does not preserve the original ordering of object pairs. Below is the screenshot from the apache pivot web page and this is my desired TreeView-based JSON viewer functionality: Hide
image
Here we goJ I have decided to create my JSON tree view component as a descendant of Delphi VCL “TTreeView” component. A good Delphi programming practice would be to derive from “TCustomTreeView” instead to be able to decide which inherited “protected” members of a class should be declared as “published”. In my case I want the end user to have access to whole TTreeView component functionality at design-time, so I do not need to hide any inherited properties. unit jsontreeview; // … type TJSONTreeView = class(TTreeView) // … public procedure LoadJson; published property JSONDocument: TJSONDocument // … property VisibleChildrenCounts: Boolean // …
property VisibleByteSizes: Boolean // … end; Additionally to the original viewer feature set I have also added the possibility to display children counts next to every non-empty JSON object or array, and estimated byte size of a given JSON tree node. The main functionality of this component is implemented in its public “LoadJson” procedure that populates the tree view based on the content of connected “TJSONDocument” component. procedure TJSONTreeView.LoadJson; var v: TJSONValue; currNode: TTreeNode; i, aCount: integer; s: string; begin ClearAll; if (JSONDocument <> nil) and JSONDocument.IsActive then begin v := JSONDocument.RootValue; Items.Clear; if TJSONDocument.IsSimpleJsonValue(v) then Items.AddChild(nil, TJSONDocument.UnQuote(v.Value)) else if v is TJSONObject then begin aCount := TJSONObject(v).Size; s := '{}'; if VisibleChildrenCounts then s := s + ' (' + IntToStr(aCount) + ')'; if VisibleByteSizes then s := s + ' (size: ' + IntToStr(v.EstimatedByteSize) + ' bytes)'; currNode := Items.AddChild(nil, s); for i := 0 to aCount - 1 do ProcessPair(currNode, TJSONObject(v), i) end else if v is TJSONArray then begin aCount := TJSONArray(v).Size; s := '[]'; if VisibleChildrenCounts then s := s + ' (' + IntToStr(aCount) + ')'; if VisibleByteSizes then
s := s + ' (size: ' + IntToStr(v.EstimatedByteSize) + ' bytes)'; currNode := Items.AddChild(nil, s); for i := 0 to aCount - 1 do ProcessElement(currNode, TJSONArray(v), i) end else raise EUnknownJsonValueDescendant.Create; FullExpand; end; end; procedure TJSONTreeView.ProcessPair(currNode: TTreeNode; obj: TJSONObject; aIndex: integer); var p: TJSONPair; s: string; n: TTreeNode; i, aCount: integer; begin p := obj.Get(aIndex); s := TJSONDocument.UnQuote(p.JsonString.ToString) + ' : '; if TJSONDocument.IsSimpleJsonValue(p.JsonValue) then begin Items.AddChild(currNode, s + p.JsonValue.ToString); exit; end; if p.JsonValue is TJSONObject then begin aCount := TJSONObject(p.JsonValue).Size; s := s + ' {}'; if VisibleChildrenCounts then s := s + ' (' + IntToStr(aCount) + ')'; if VisibleByteSizes then s := s + ' (size: ' + IntToStr(p.EstimatedByteSize) + ' bytes)'; n := Items.AddChild(currNode, s); for i := 0 to aCount - 1 do ProcessPair(n, TJSONObject(p.JsonValue), i); end else if p.JsonValue is TJSONArray then begin
aCount := TJSONArray(p.JsonValue).Size; s := s + ' []'; if VisibleChildrenCounts then s := s + ' (' + IntToStr(aCount) + ')'; if VisibleByteSizes then s := s + ' (size: ' + IntToStr(p.EstimatedByteSize) + ' bytes)'; n := Items.AddChild(currNode, s); for i := 0 to aCount - 1 do ProcessElement(n, TJSONArray(p.JsonValue), i); end else raise EUnknownJsonValueDescendant.Create; end; procedure TJSONTreeView.ProcessElement(currNode: TTreeNode; arr: TJSONArray; aIndex: integer); var v: TJSONValue; s: string; n: TTreeNode; i, aCount: integer; begin v := arr.Get(aIndex); s := '[' + IntToStr(aIndex) + '] '; if TJSONDocument.IsSimpleJsonValue(v) then begin Items.AddChild(currNode, s + v.ToString); exit; end; if v is TJSONObject then begin aCount := TJSONObject(v).Size; s := s + ' {}'; if VisibleChildrenCounts then s := s + ' (' + IntToStr(aCount) + ')'; if VisibleByteSizes then s := s + ' (size: ' + IntToStr(v.EstimatedByteSize) + ' bytes)'; n := Items.AddChild(currNode, s); for i := 0 to aCount - 1 do ProcessPair(n, TJSONObject(v), i); end else if v is TJSONArray then begin
aCount := TJSONArray(v).Size; s := s + ' []'; n := Items.AddChild(currNode, s); if VisibleChildrenCounts then s := s + ' (' + IntToStr(aCount) + ')'; if VisibleByteSizes then s := s + ' (size: ' + IntToStr(v.EstimatedByteSize) + ' bytes)'; for i := 0 to aCount - 1 do ProcessElement(n, TJSONArray(v), i); end else raise EUnknownJsonValueDescendant.Create; end;
JSON Standalone Viewer In the next step I have used “TJSONDocument” and “TJSONTreeView” components to implement a simple JSON Viewer application. The functionality is minimal. You can clear the current contents of the JSON viewer using “Clear” button, or you can copy to clipboard a JSON text and paste it into the viewer window using “Paste” button. There is also a popup menu to control if children counts and node byte sizes are displayed or not. The application icon was created using IcoFX (http://icofx.ro/) directly from the JSON logo downloaded from the JSON home page. Below is the screenshot from Delphi JSON Viewer at runtime. Just copy some JSON text to clipboard and paste… Hide
image
TJSONParser Component In a sense the “TJSONDocument” component can be considered the implementation of Document Object Model for JSON. But what about SAX for JSON? SAX – or Simple API for XML – presents a completely different approach to document parsing. Instead of building an in-memory representation of the document, it just goes through it and fires events for every syntactical element encountered. It is up to application to process the events it is interested in. For example to find something inside a large document. Based on the “TJSONDocument” I have implemented an experimental “TJSONParser” component that implements SAX processing model for JSON. In reality the “SAX parser” for
JSON should be implemented from scratch and directly parse JSON text and fire relevant events. In my case it sits on top of the in-memory representation of JSON. The “jsonparser” unit contains the following enumerated type that lists different token types that can be found in a JSON text: type TJSONTokenKind = ( jsNumber, jsString, jsTrue, jsFalse, jsNull, jsObjectStart, jsObjectEnd, jsArrayStart, jsArrayEnd, jsPairStart, jsPairEnd ); There is also a declaration of “TJSONTokenEvent” that is fired when a JSON token is encountered: type TJSONTokenEvent = procedure( ATokenKind: TJSONTokenKind; AContent: string) of object; The “TJSONParser” class is derived from “TJSONDocument” and declared as follows: type TJSONParser = class(TJSONDocument) private FOnToken: TJSONTokenEvent; FTokenList: TJSONTokenList; procedure DoOnAddToTokenListEvent( ATokenKind: TJSONTokenKind; AContent: string); procedure DoOnFireTokenEvent( ATokenKind: TJSONTokenKind; AContent: string); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure FireTokenEvents; procedure BuildTokenList; procedure DoProcess(val: TJSONValue; aTokenProc: TJSONTokenProc); property TokenList: TJSONTokenList read FTokenList; published property OnToken: TJSONTokenEvent read FOnToken write FOnToken; end; The “TJSONParser” component can do two things. If you call “FireTokenEvents” public method,
it will traverse the underlying JSON document and fire “OnToken” events for every token it encounters. It is also possible to build a token list in memory that can be accessed via “TokenList” property. This could be useful if we would like to implement JSON viewer using the Virtual Tree View component that requires fast access to the underlying data structure. What was interesting during the implementation of these two methods was the fact the underlying documental traversal algorithm was the same for both firing the events and building the list. In order to avoid code duplication, I have decided to parameterize the traversal algorithm using anonymous methods. The following anonymous method signature was defined in “jsonparser” unit: type TJSONTokenProc = reference to procedure( ATokenKind: TJSONTokenKind; AContent: string); The signature of this method matches “DoOnAddToTokenListEvent” and “DoOnFireTokenEvent” private methods in the declaration of the “TJSONParser” class. The actual document traversal algorithm has been implemented inside “DoProcess” method that is called from both “FireTokenEvents” and “BuildTokenList” method in the following way: procedure TJSONParser.BuildTokenList; begin if RootValue <> nil then DoProcess(RootValue, DoOnAddToTokenListEvent); end; procedure TJSONParser.FireTokenEvents; begin if RootValue <> nil then DoProcess(RootValue, DoOnFireTokenEvent); end; procedure TJSONParser.DoOnFireTokenEvent(ATokenKind: TJSONTokenKind; AContent: string); begin if Assigned(FOnToken) then FOnToken(ATokenKind, AContent); end; procedure TJSONParser.DoOnAddToTokenListEvent(ATokenKind: TJSONTokenKind; AContent: string); begin FTokenList.Add(ATokenKind, AContent);
end; In this way we have both functionalities implemented without code duplication inside the recursive “DoProcess” method: procedure TJSONParser.DoProcess(val: TJSONValue; aTokenProc: TJSONTokenProc); var i: integer; begin if val is TJSONNumber then aTokenProc(jsNumber, TJSONNumber(val).Value) else if val is TJSONString then aTokenProc(jsString, TJSONString(val).Value) else if val is TJSONTrue then aTokenProc(jsTrue, 'true') else if val is TJSONFalse then aTokenProc(jsFalse, 'false') else if val is TJSONNull then aTokenProc(jsNull, 'null') else if val is TJSONArray then begin aTokenProc(jsArrayStart, ''); with val as TJSONArray do for i := 0 to Size - 1 do DoProcess(Get(i), aTokenProc); aTokenProc(jsArrayEnd, ''); end else if val is TJSONObject then begin aTokenProc(jsObjectStart, ''); with val as TJSONObject do for i := 0 to Size - 1 do begin aTokenProc(jsPairStart, Get(i).JsonString.ToString); DoProcess(Get(i).JsonValue, aTokenProc); aTokenProc(jsPairEnd, ''); end;
aTokenProc(jsObjectEnd, ''); end else raise EUnknownJsonValueDescendant.Create; end; The “TJSONParser” component can be used as a starting for arbitrary JSON processing at the lowest level of actual JSON text tokens. Delphi anonymous methods are really coolJ
Summary JSON is currently probably the most important data interchange format in use. Its simplicity makes it easy to process and information encoded with JSON is typically smaller than using XML. Over the years XML has become the whole family of specifications and it is not a trivial task to implement fully compliant XML parser from scratch. Delphi 6 was the first commercial IDE on the market to introduce support for XML SOAP web services. Delphi 6 also introduced TXMLDocument component and XML Data Binding Wizard to make it easier to work with XML. In the world of JSON slowly emerges the equivalent of XML Schema for JSON, which tries to create a meta representation of JSON. On the JSON home page you can find a reference to a draft version of IETF RFC “A JSON Media Type for Describing the Structure and Meaning of JSON Documents” [8]. This is still pending feedback but in future could be a starting point for implementing a “Data Binding Wizard for JSON”. In this article I have described a JSON Viewer application implemented with Embarcadero Delphi XE. The source code that accompanies this paper is organized in the form of two packages for Delphi components – one runtime and one design-time – and the “djsonview”: Delphi VCL Forms application that implements the Delphi JSON Viewer. The full source code described in this paper can be downloaded from [1].