Author: Michael K22532
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]
See “References” section at the end of this article.
Table of Contents
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 *** 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.
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "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.
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:
1 2 3 4 5 |
TDBXCallback = <code class="keyword">class</code> <code class="keyword">abstract public function</code> Execute(<code class="keyword">const</code> Arg: TJSONValue): TJSONValue; <code class="keyword">virtual</code>; <code class="keyword">abstract</code>; // … other members stripped out end</code>; |
The DBXJSON unit contains also functionality to parse JSON text into the graph of TJSONValue-descedants 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
unit</code> jsondoc; //… type TJSONDocument = <code class="keyword">class</code>(TComponent) private FRootValue: TJSONValue; FJsonText: <code class="keyword">string</code>; FOnChange: TNotifyEvent; procedure</code> SetJsonText(<code class="keyword">const</code> Value: <code class="keyword">string</code>); procedure</code> SetRootValue(<code class="keyword">const</code> Value: TJSONValue); protected procedure</code> FreeRootValue; procedure</code> DoOnChange; <code class="keyword">virtual</code>; public class</code> <code class="keyword">function</code> IsSimpleJsonValue(v: TJSONValue): boolean; <code class="keyword">inline</code>; class</code> <code class="keyword">function</code> UnQuote(s: <code class="keyword">string</code>): <code class="keyword">string</code>; <code class="keyword">inline</code>; class</code> <code class="keyword">function</code> StripNonJson(s: <code class="keyword">string</code>): <code class="keyword">string</code>; <code class="keyword">inline</code>; constructor</code> Create(AOwner: TComponent); <code class="keyword">override</code>; destructor</code> Destroy; <code class="keyword">override</code>; function</code> ProcessJsonText: boolean; function</code> IsActive: boolean; function</code> EstimatedByteSize: integer; property</code> RootValue: TJSONValue <code class="keyword">read</code> FRootValue <code class="keyword">write</code> SetRootValue; published property</code> JsonText: <code class="keyword">string</code> <code class="keyword">read</code> FJsonText <code class="keyword">write</code> SetJsonText; property</code> OnChange: TNotifyEvent <code class="keyword">read</code> FOnChange <code class="keyword">write</code> FOnChange; end</code>; |
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.
1 2 3 4 |
function</code> TJSONDocument.IsActive: boolean; begin Result := RootValue <> <code class="keyword">nil</code>; end</code>; |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class</code> <code class="keyword">function</code> TJSONDocument.StripNonJson(s: <code class="keyword">string</code>): <code class="keyword">string</code>; var</code> ch: char; inString: boolean; begin Result := <code class="quote">''</code>; inString := false; for</code> ch <code class="keyword">in</code> s <code class="keyword">do begin if</code> ch = <code class="quote">'"'</code> <code class="keyword">then inString := <code class="keyword">not</code> inString; if</code> TCharacter.IsWhiteSpace(ch) <code class="keyword">and</code> <code class="keyword">not</code> inString <code class="keyword">then continue; Result := Result + ch; end</code>; end</code>; |
The process of JSON parsing is implemented in “ProcessJsonText” method that is called as a side-effect of assigning to “JsonText: string” published property.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
procedure</code> TJSONDocument.SetJsonText(<code class="keyword">const</code> Value: <code class="keyword">string</code>); begin if</code> FJsonText <> Value <code class="keyword">then begin FreeRootValue; FJsonText := Value; if</code> FJsonText <> <code class="quote">''</code> <code class="keyword">then ProcessJsonText end</code>; end</code>; function</code> TJSONDocument.ProcessJsonText: boolean; var</code> s: <code class="keyword">string</code>; begin FreeRootValue; s := StripNonJson(JsonText); FRootValue := TJSONObject.ParseJSONValue(BytesOf(s),0); Result := IsActive; DoOnChange; end</code>; |
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 like http://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/json-viewer.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:
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.
1 2 3 4 5 6 7 8 9 10 11 12 |
unit</code> jsontreeview; // … type TJSONTreeView = <code class="keyword">class</code>(TTreeView) // … public procedure</code> LoadJson; published property</code> JSONDocument: TJSONDocument <code class="comment">// … property</code> VisibleChildrenCounts: Boolean <code class="comment">// … property</code> VisibleByteSizes: Boolean <code class="comment">// … end</code>; |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
procedure</code> TJSONTreeView.LoadJson; var</code> v: TJSONValue; currNode: TTreeNode; i, aCount: integer; s: <code class="keyword">string</code>; begin ClearAll; if</code> (JSONDocument <> <code class="keyword">nil</code>) <code class="keyword">and</code> JSONDocument.IsActive <code class="keyword">then begin v := JSONDocument.RootValue; Items.Clear; if</code> TJSONDocument.IsSimpleJsonValue(v) <code class="keyword">then Items.AddChild(<code class="keyword">nil</code>, TJSONDocument.UnQuote(v.Value)) else</code> <code class="keyword">if</code> v <code class="keyword">is</code> TJSONObject <code class="keyword">then begin aCount := TJSONObject(v).Size; s := <code class="quote">'{}'</code>; if</code> VisibleChildrenCounts <code class="keyword">then s := s + <code class="quote">' ('</code> + IntToStr(aCount) + <code class="quote">')'</code>; if</code> VisibleByteSizes <code class="keyword">then s := s + <code class="quote">' (size: '</code> + IntToStr(v.EstimatedByteSize) + <code class="quote">' bytes)'</code>; currNode := Items.AddChild(<code class="keyword">nil</code>, s); for</code> i := 0 <code class="keyword">to</code> aCount - 1 <code class="keyword">do ProcessPair(currNode, TJSONObject(v), i) end else</code> <code class="keyword">if</code> v <code class="keyword">is</code> TJSONArray <code class="keyword">then begin aCount := TJSONArray(v).Size; s := <code class="quote">'[]'</code>; if</code> VisibleChildrenCounts <code class="keyword">then s := s + <code class="quote">' ('</code> + IntToStr(aCount) + <code class="quote">')'</code>; if</code> VisibleByteSizes <code class="keyword">then s := s + <code class="quote">' (size: '</code> + IntToStr(v.EstimatedByteSize) + <code class="quote">' bytes)'</code>; currNode := Items.AddChild(<code class="keyword">nil</code>, s); for</code> i := 0 <code class="keyword">to</code> aCount - 1 <code class="keyword">do ProcessElement(currNode, TJSONArray(v), i) end else raise</code> EUnknownJsonValueDescendant.Create; FullExpand; end</code>; end</code>; procedure</code> TJSONTreeView.ProcessPair(currNode: TTreeNode; obj: TJSONObject; aIndex: integer); var</code> p: TJSONPair; s: <code class="keyword">string</code>; n: TTreeNode; i, aCount: integer; begin p := obj.Get(aIndex); s := TJSONDocument.UnQuote(p.JsonString.ToString) + <code class="quote">' : '</code>; if</code> TJSONDocument.IsSimpleJsonValue(p.JsonValue) <code class="keyword">then begin Items.AddChild(currNode, s + p.JsonValue.ToString); exit; end</code>; if</code> p.JsonValue <code class="keyword">is</code> TJSONObject <code class="keyword">then begin aCount := TJSONObject(p.JsonValue).Size; s := s + <code class="quote">' {}'</code>; if</code> VisibleChildrenCounts <code class="keyword">then s := s + <code class="quote">' ('</code> + IntToStr(aCount) + <code class="quote">')'</code>; if</code> VisibleByteSizes <code class="keyword">then s := s + <code class="quote">' (size: '</code> + IntToStr(p.EstimatedByteSize) + <code class="quote">' bytes)'</code>; n := Items.AddChild(currNode, s); for</code> i := 0 <code class="keyword">to</code> aCount - 1 <code class="keyword">do ProcessPair(n, TJSONObject(p.JsonValue), i); end else</code> <code class="keyword">if</code> p.JsonValue <code class="keyword">is</code> TJSONArray <code class="keyword">then begin aCount := TJSONArray(p.JsonValue).Size; s := s + <code class="quote">' []'</code>; if</code> VisibleChildrenCounts <code class="keyword">then s := s + <code class="quote">' ('</code> + IntToStr(aCount) + <code class="quote">')'</code>; if</code> VisibleByteSizes <code class="keyword">then s := s + <code class="quote">' (size: '</code> + IntToStr(p.EstimatedByteSize) + <code class="quote">' bytes)'</code>; n := Items.AddChild(currNode, s); for</code> i := 0 <code class="keyword">to</code> aCount - 1 <code class="keyword">do ProcessElement(n, TJSONArray(p.JsonValue), i); end else raise</code> EUnknownJsonValueDescendant.Create; end</code>; procedure</code> TJSONTreeView.ProcessElement(currNode: TTreeNode; arr: TJSONArray; aIndex: integer); var</code> v: TJSONValue; s: <code class="keyword">string</code>; n: TTreeNode; i, aCount: integer; begin v := arr.Get(aIndex); s := <code class="quote">'['</code> + IntToStr(aIndex) + <code class="quote">'] '</code>; if</code> TJSONDocument.IsSimpleJsonValue(v) <code class="keyword">then begin Items.AddChild(currNode, s + v.ToString); exit; end</code>; if</code> v <code class="keyword">is</code> TJSONObject <code class="keyword">then begin aCount := TJSONObject(v).Size; s := s + <code class="quote">' {}'</code>; if</code> VisibleChildrenCounts <code class="keyword">then s := s + <code class="quote">' ('</code> + IntToStr(aCount) + <code class="quote">')'</code>; if</code> VisibleByteSizes <code class="keyword">then s := s + <code class="quote">' (size: '</code> + IntToStr(v.EstimatedByteSize) + <code class="quote">' bytes)'</code>; n := Items.AddChild(currNode, s); for</code> i := 0 <code class="keyword">to</code> aCount - 1 <code class="keyword">do ProcessPair(n, TJSONObject(v), i); end else</code> <code class="keyword">if</code> v <code class="keyword">is</code> TJSONArray <code class="keyword">then begin aCount := TJSONArray(v).Size; s := s + <code class="quote">' []'</code>; n := Items.AddChild(currNode, s); if</code> VisibleChildrenCounts <code class="keyword">then s := s + <code class="quote">' ('</code> + IntToStr(aCount) + <code class="quote">')'</code>; if</code> VisibleByteSizes <code class="keyword">then s := s + <code class="quote">' (size: '</code> + IntToStr(v.EstimatedByteSize) + <code class="quote">' bytes)'</code>; for</code> i := 0 <code class="keyword">to</code> aCount - 1 <code class="keyword">do ProcessElement(n, TJSONArray(v), i); end else raise</code> EUnknownJsonValueDescendant.Create; end</code>; |
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…
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:
1 2 3 4 5 6 7 |
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:
1 2 3 |
type TJSONTokenEvent = <code class="keyword">procedure</code>( ATokenKind: TJSONTokenKind; AContent: <code class="keyword">string</code>) <code class="keyword">of</code> <code class="keyword">object</code>; |
The “TJSONParser” class is derived from “TJSONDocument” and declared as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
type TJSONParser = <code class="keyword">class</code>(TJSONDocument) private FOnToken: TJSONTokenEvent; FTokenList: TJSONTokenList; procedure</code> DoOnAddToTokenListEvent( ATokenKind: TJSONTokenKind; AContent: <code class="keyword">string</code>); procedure</code> DoOnFireTokenEvent( ATokenKind: TJSONTokenKind; AContent: <code class="keyword">string</code>); public constructor</code> Create(AOwner: TComponent); <code class="keyword">override</code>; destructor</code> Destroy; <code class="keyword">override</code>; procedure</code> FireTokenEvents; procedure</code> BuildTokenList; procedure</code> DoProcess(val: TJSONValue; aTokenProc: TJSONTokenProc); property</code> TokenList: TJSONTokenList <code class="keyword">read</code> FTokenList; published property</code> OnToken: TJSONTokenEvent <code class="keyword">read</code> FOnToken <code class="keyword">write</code> FOnToken; end</code>; |
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:
1 2 3 |
type TJSONTokenProc = reference <code class="keyword">to</code> <code class="keyword">procedure</code>( ATokenKind: TJSONTokenKind; AContent: <code class="keyword">string</code>); |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
procedure</code> TJSONParser.BuildTokenList; begin if</code> RootValue <> <code class="keyword">nil</code> <code class="keyword">then DoProcess(RootValue, DoOnAddToTokenListEvent); end</code>; procedure</code> TJSONParser.FireTokenEvents; begin if</code> RootValue <> <code class="keyword">nil</code> <code class="keyword">then DoProcess(RootValue, DoOnFireTokenEvent); end</code>; procedure</code> TJSONParser.DoOnFireTokenEvent(ATokenKind: TJSONTokenKind; AContent: <code class="keyword">string</code>); begin if</code> Assigned(FOnToken) <code class="keyword">then FOnToken(ATokenKind, AContent); end</code>; procedure</code> TJSONParser.DoOnAddToTokenListEvent(ATokenKind: TJSONTokenKind; AContent: <code class="keyword">string</code>); begin FTokenList.Add(ATokenKind, AContent); end</code>; |
In this way we have both functionalities implemented without code duplication inside the recursive “DoProcess” method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
procedure</code> TJSONParser.DoProcess(val: TJSONValue; aTokenProc: TJSONTokenProc); var</code> i: integer; begin if</code> val <code class="keyword">is</code> TJSONNumber <code class="keyword">then aTokenProc(jsNumber, TJSONNumber(val).Value) else</code> <code class="keyword">if</code> val <code class="keyword">is</code> TJSONString <code class="keyword">then aTokenProc(jsString, TJSONString(val).Value) else</code> <code class="keyword">if</code> val <code class="keyword">is</code> TJSONTrue <code class="keyword">then aTokenProc(jsTrue, <code class="quote">'true'</code>) else</code> <code class="keyword">if</code> val <code class="keyword">is</code> TJSONFalse <code class="keyword">then aTokenProc(jsFalse, <code class="quote">'false'</code>) else</code> <code class="keyword">if</code> val <code class="keyword">is</code> TJSONNull <code class="keyword">then aTokenProc(jsNull, <code class="quote">'null'</code>) else</code> <code class="keyword">if</code> val <code class="keyword">is</code> TJSONArray <code class="keyword">then begin aTokenProc(jsArrayStart, <code class="quote">''</code>); with</code> val <code class="keyword">as</code> TJSONArray <code class="keyword">do for</code> i := 0 <code class="keyword">to</code> Size - 1 <code class="keyword">do DoProcess(Get(i), aTokenProc); aTokenProc(jsArrayEnd, <code class="quote">''</code>); end else</code> <code class="keyword">if</code> val <code class="keyword">is</code> TJSONObject <code class="keyword">then begin aTokenProc(jsObjectStart, <code class="quote">''</code>); with</code> val <code class="keyword">as</code> TJSONObject <code class="keyword">do for</code> i := 0 <code class="keyword">to</code> Size - 1 <code class="keyword">do begin aTokenProc(jsPairStart, Get(i).JsonString.ToString); DoProcess(Get(i).JsonValue, aTokenProc); aTokenProc(jsPairEnd, <code class="quote">''</code>); end</code>; aTokenProc(jsObjectEnd, <code class="quote">''</code>); end else raise</code> EUnknownJsonValueDescendant.Create; end</code>; |
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].
References
- Source code for this article (http://cc.embarcadero.com/item/27788)
- Source code for this article as part of the RAD Studio XE demos on sourceforge (https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio_XE/Delphi/DataSnap/JSONViewer)
- JSON RFC (http://www.ietf.org/rfc/rfc4627.txt)
- JSON: The Fat-Free Alternative to XML (http://www.json.org/fatfree.html)
- JSON Home Page (http://www.json.org)
- JSON Examples (http://www.json.org/example.html)
- Apache Pivot JSON Viewer (http://pivot.apache.org/demos/json-viewer.html)
- Simple API for XML Home Page (http://www.megginson.com/downloads/SAX/)
- Draft JSON Schema (http://tools.ietf.org/html/draft-zyp-json-schema-02)
- Embarcadero Delphi Home Page (http://www.embarcadero.com/products/delphi )
About the Author
Paweł Głowacki is Embarcadero Technologies’ European Technical Lead for Delphi, RAD Studio and All-Access technologies. Previously, Paweł spent over 7 years working as a senior consultant and trainer for Delphi within Borland Education Services and CodeGear. As well as working with Embarcadero customers across the region, he also represents Embarcadero internationally as a conference and seminar speaker.
For more information check out Paweł’s technical blog at http://blogs.embarcadero.com/pawelglowacki
Embarcadero Technologies, Inc. is a leading provider of award-winning tools for application developers and database professionals so they can design systems right, build them faster and run them better, regardless of their platform or programming language. Ninety of the Fortune 100 and an active community of more than three million users worldwide rely on Embarcadero products to increase productivity, reduce costs, simplify change management and compliance and accelerate innovation. The company’s flagship tools include: Embarcadero® Change Manager™, Embarcadero™ RAD Studio, DBArtisan®, Delphi®, ER/Studio®, JBuilder® and Rapid SQL®. Founded in 1993, Embarcadero is headquartered in San Francisco, with offices located around the world. Embarcadero is online at www.embarcadero.com.
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition