User Guide

Contents

Preface

This document is a living document! As always read and try out the code to understand what's really going on.

About the project

The eJSON project was started by Javier Velilla in 2008. The aim was simply to provide JSON support to Eiffel programmers. A couple of other people have been involved to various extent since the start; Berend de Boer, Jocelyn Fiat and Manu Stapf. In 2009 Paul Cohen joined the project as an active developer.

The formal name of the project is "eJSON".

For questions regarding eJSON please contact <javier.hector at gmail.com> or <paul.cohen at seibostudios.se>

Current version and status

The latest release is 0.2.0. eJSON is still very much in beta status. It has been used in a few applications, but may still be subject to some substantial refactoring and changes to exported classes. We are currently working on the next release 0.3.0 that will consolidate the current code base to a more stable state.

Projects that use eJSON

Introduction

What is JSON?

JSON (JavaScript Object Notation) is a lightweight computer data interchange format. It is a text-based, human-readable format for representing simple data structures and associative arrays (called objects). See the Wikipedia article on JSON, www.json.org and www.json.com for more information.

The JSON format is specified in IETF RFC 4627 by Douglas Crockford. The official Internet MIME media type for JSON is "application/json". The recommended file name extension for JSON files is ".json".

Advantages

1. Lightweight data-interchange format.

2. Easy for humans to read and write.

3. Enables easy integration with AJAX/JavaScript web applications. See the article Speeding Up AJAX with JSON for a good short discussion on this subject.

4. JSON data structures translate with ease into the native data structures universal to almost all programming languages used today.

Use in Eiffel applications

JSON can be used as a general serialization format for Eiffel objects. As such it could be used as a:

  • Data representation format in REST-based web service applications written in Eiffel.
  • Serialization format for Eiffel objects in persistence solutions.
  • File format for configuration files in Eiffel systems.

Prerequisites

eJSON works today with EiffelStudio 6.5. It also requires the latest snapshot of the Gobo Eiffel libraries (a working snapshot is distributed with EiffelStudio 6.5). The depencencies on Gobo are on Gobo:s unicode and regexp libraries and for some of the extra features in eJSON, on Gobos structure classes DS_HASH_TABLE and DS_LINKED_LIST.

We intend eJSON to work with all ECMA compliant Eiffel compilers.

Installation

You can either download a given release and install on your machine or you can get the latest snapshot of the code. To download go to the download page. To get the latest snapshot of the code do:

  1. $ svn checkout https://ejson.origo.ethz.ch/svn/trunk ejson

Cluster and directory layout

  1. json
  2.   library (Root directory for eJSON library classes)
  3.     kernel (All classes in this cluster should eventually only depend on ECMA Eiffel and FreeELKS).
  4.       json_array.e
  5.       json_boolean.e
  6.       json_null.e
  7.       json_number.e
  8.       json_object.e
  9.       json_string.e
  10.       json_value.e
  11.       ejson.e
  12.       shared_ejson.e
  13.       scanner (eJSON parser classes)
  14.         json_parser.e
  15.         json_reader.e
  16.         json_tokens.e
  17.       converters (eJSON core converter classes)
  18.         json_converter.e
  19.         json_hash_table_converter.e
  20.         json_linked_list_converter.e
  21.     gobo (eJSON support for Gobo)
  22.       shared_gobo_ejson.e
  23.       converters
  24.         json_ds_hash_table_converter.e
  25.         json_ds_linked_list_converter.e
  26.     extras (Various useful eJSON classes)
  27.       visitor (eJSON visitor pattern)
  28.         JSON_VISITOR
  29.         JSON_PRINTER
  30.       file
  31.         JSON_FILE_READER
  32.   build (Contains build scripts)
  33.   test (Contains test code)
  34.     geant (geant based unit test).
  35.     autotest (AutoTest based unit test).
  36.   example (Example code)

Future development

Here is a list of suggestions for future development of eJSON.

  • Ongoing: Provide a JSON_FACTORY class for easy conversion between arbitrary JSON and Eiffel values.
  • Ongoing: Provide a mechanism for users to add custom converters between JSON values and user space Eiffel classes.
  • Ongoing: Implement a full test framework for eJSON.
  • Ongoing: Add an example application.
  • Suggestion: Investigate performance and improve it if neccessary.
  • Suggestion: Add support to JSON classes for conversion from Eiffel manifest values. So one can write things like:

A simple example

There are two basic approaches to using eJSON; either you use the basic JSON_VALUE classes, converting to and from JSON values to corresponding Eiffel instances or you use the high level eJSON interface class SHARED_EJSON. Of course you can use a mix of both approaches if you find it appropriate!

Here is an example of how to create a JSON number value from an INTEGER and then obtain the JSON representation for that value.

  1.     simple_example is
  2.         local
  3.             i: INTEGER
  4.             jn: JSON_NUMBER
  5.             s: STRING
  6.         do
  7.             i := 42
  8.             create jn.make_integer (i)
  9.             s := jn.representation -- s.is_equal ("42")
  10.         end

Here is an example of how to do the same using SHARED_EJSON.

  1. class APPLICATION
  2.  
  3. inherit
  4.     {NONE} SHARED_EJSON
  5.  
  6. feature
  7.  
  8.     simple_example_2 is
  9.         local
  10.             i: INTEGER
  11.             jn: JSON_NUMBER
  12.             s: STRING
  13.         do
  14.             i := 42
  15.             jn ?= json.value (i)
  16.             s := jn.representation -- s.is_equal ("42")
  17.         end

The class SHARED_EJSON makes the feature "json" available which is bound to a once instance of EJSON. The use of SHARED_EJSON is typically more motivated when you have more complex Eiffel class instances that you want to convert to/from JSON.

Mapping of JSON values to Eiffel values

The mappings described here are the default mappings used by eJSON. A user can add custom mappings by subclassing the JSON_CONVERTER class with a custom converter class for a specific type of JSON value and custom Eiffel type value.

First a note on the code examples. As mentioned in the previous chapter you can either work with eJSON by using the JSON_VALUE classes or by non-conforming inheritance of SHARED_EJSON and by using the inherited "json" feature. For sake of brevity the rest of the code examples don't explicitly state the inheritance. You should assume that each code example is in a class that has a declared non-conbforming inheritance clause for SHARED_EJSON.

JSON number

JSON numbers are represented by the class JSON_NUMBER. JSON number values can be converted to/from NATURAL_*, INTEGER_* and REAL_64 values. For floating point values REAL_* is used. The complete mapping is as follows:

JSON number -> Eiffel:

* -128 <= n <= +127 -> INTEGER_8
* n can't be represented by INTEGER_8 and -32768 <= n <= +32767 -> INTEGER_16
* n can't be represented by INTEGER_16 and -2147483648 <= n <= +2147483647 -> INTEGER_32
* n can't be represented by INTEGER_32 and -9223372036854775808 <= n <= +9223372036854775807 -> INTEGER_64 
* n can't be represented by INTEGER_64 and 9223372036854775808 <= n <= 18446744073709551615 -> NATURAL_64
* n has fractional dot '.' -> REAL_64.
* n -> eJSON exception if number can't be represented by a INTEGER_64, NATURAL_64 or REAL_64.

Eiffel -> JSON number:

* NATURAL_8, NATURAL_16, NATURAL_32, NATURAL_64, NATURAL -> JSON number
* INTEGER_8, INTEGER_16, INTEGER_32, INTEGER_64, INTEGER -> JSON number
* REAL_32, REAL_64, REAL -> JSON number

You can use the following creation routines to create JSON_NUMBER instances:

  • JSON_NUMBER.make_integer
  • JSON_NUMBER.make_real
  • JSON_NUMBER.make_natural

  1.     eiffel_to_json_number_representation is
  2.         local
  3.             i: INTEGER
  4.             r: REAL
  5.             jn: JSON_NUMBER
  6.         do
  7.             print ("JSON representation of Eiffel INTEGER: '")
  8.             i := 123
  9.             create jn.make_integer (i)
  10.             print (jn.representation)
  11.             print ("'%N")
  12.             print ("JSON representation of Eiffel REAL: '")
  13.             r := 12.3
  14.             create jn.make_real (r)
  15.             print (jn.representation)
  16.             print ("'%N")
  17.         end

The output of the above code will be:

  1. JSON representation of Eiffel INTEGER: '123'
  2. JSON representation of Eiffel REAL: '12.300000190734863'

JSON boolean

JSON boolean values are represented by the class JSON_BOOLEAN. The JSON boolean value "true" is converted to/from the BOOLEAN value "True" and the JSON boolean value "false is converted to/from the BOOLEAN value "False".

  1.     eiffel_to_json_boolean_representation is
  2.         local
  3.             b: BOOLEAN
  4.             jb: JSON_BOOLEAN
  5.         do
  6.             print ("JSON representation of Eiffel BOOLEAN: '")
  7.             b := True
  8.             create jb.make_boolean (b)
  9.             print (jb.representation)
  10.             print ("'%N")
  11.             print("JSON representation of Eiffel BOOLEAN: '")
  12.             b := False
  13.             create jb.make_boolean (b)
  14.             print (jb.representation)
  15.             print ("'%N")
  16.         end

The output of the above code will be:

  1. JSON representation of Eiffel BOOLEAN: 'true'
  2. JSON representation of Eiffel BOOLEAN: 'false'

JSON string

JSON strings are represented by the class JSON_STRING. JSON string values can be converted to/from UC_STRING, STRING and CHARACTER values. The complete mapping is as follows:

JSON string -> Eiffel:

* All JSON strings -> UC_STRING

Eiffel -> JSON string:

* UC_STRING -> JSON string
* STRING -> JSON string
* CHARACTER -> JSON string

  1.     eiffel_to_json_string_representation is
  2.         local
  3.             s: STRING
  4.             js: JSON_STRING
  5.         do
  6.             print ("JSON representation of Eiffel STRING: '")
  7.             s := "JSON rocks!"
  8.             create js.make_json (s)
  9.             print (js.representation)
  10.             print ("'%N")
  11.         end

The output of the above code will be:

  1. JSON representation of Eiffel STRING: '"JSON rocks!"'

JSON null

The JSON null value is represented by the class JSON_NULL. The JSON null value can be converted to/from Void.

  1.     eiffel_to_json_null_representation is
  2.         local
  3.             a: ANY
  4.             jn: JSON_NULL
  5.         do
  6.             create jn
  7.             print ("JSON representation for JSON null value: '")
  8.             print (jn.representation)
  9.             print ("'%N")
  10.             a := Void
  11.             jn ?= json.value (a) -- json from SHARED_EJSON!
  12.             check jn /= Void end
  13.             print ("JSON representation of Eiffel Void reference: '")
  14.             print (jn.representation)
  15.             print ("'%N")
  16.         end

The output of the above code will be:

  1. JSON representation for JSON null value: 'null'
  2. JSON representation of Eiffel Void reference: 'null'

JSON array

JSON array is represented by the class JSON_ARRAY. JSON arrays are by default converted to/from LINKED_LIST, but you can also choose to use Gobo:s DS_LINKED_LIST instead. The conversion will fail if a value of the array is based on a type (or rather base class) for which no default conversion exists and for which no user specified converter is found.

JSON object

JSON object is represented by the class JSON_OBJECT. JSON objects are by default converted to/from HASH_TABLE [ANY, UC_STRING], but you can choose to use Gobo:s DS_HASH_TABLE instead. The conversion will fail if a value of the hash table is based on a type (or rather base class) for which no default conversion exists and for which no user specified converter is found.

EJSON, SHARED_EJSON and the converter classes

The class EJSON contains the high level client interface to the eJSON library. It contains the three basic features that the typical user of eJSON is interested in:

  1.     value (an_object: ?ANY): JSON_VALUE is
  2.             -- JSON value from Eiffel object. Raises an "eJSON exception" if
  3.             -- unable to convert value.
  4.  
  5.     object (a_value: JSON_VALUE; base_class: ?STRING): ANY is
  6.             -- Eiffel object from JSON value. If `base_class' /= Void an eiffel
  7.             -- object based on `base_class' will be returned. Raises an "eJSON
  8.             -- exception" if unable to convert value.
  9.  
  10.     object_from_json (json: STRING; base_class: ?STRING): ANY is
  11.             -- Eiffel object from JSON representation. If `base_class' /= Void an
  12.             -- Eiffel object based on `base_class' will be returned. Raises an
  13.             -- "eJSON exception" if unable to convert value.

Using the above features will convert Eiffel objects to/from JSON values as specified by the Eiffel/JSON mapping above. More specifically the two features `object' and `object_from_json' will return Eiffel objects of default types for the given JSON value if the parameter `base_class' is Void. If `base_class' is not Void, the conversion will be to an Eiffel object of a type based on the `base_class' if such a conversion is possible.

In order to ensure the same EJSON instance is shared by all different objects needing to do conversions, you should inherit from SHARED_EJSON (using non-conforming inheritance). You can then access the shared EJSON instance via the `json' feature in SHARED_EJSON.

Important Note: The EJSON features above raise an exception if unable to convert. This solution may be changed in the future to a controlled approach where the features return Void if unable to convert and a query feature is available to query why the conversion failed. However `object' and `object_from_json' will still/also return Void if passed a JSON_NULL object or "null" string respectively!

A client can add conversion support for arbitrary Eiffel classes by subclassing the JSON_CONVERTER class and then registering an instance of your converter with an instance of EJSON. You can of course also write you own custom MY_SHARED_EJSON class that sets up EJSON the way you want.

Using the Gobo converters

You can use the SHARED_GOBO_EJSON class instead of SHARED_EJSON if you want to map JSON objects to DS_HASH_TABLE:s instead of HASH_TABLE and JSON arrays to DS_LINKED_LISTS:s instead of LINKED_LIST.

Adding your own custom converters

Here is a simple example of how SHARED_EJSON can be used with a custom JSON_FOOBAR_CONVERTER class than can convert between JSON object values and FOOBAR objects (we ignore the exception handling below to keep the example simple).

  1. class FOOBAR_INTERFACE
  2.  
  3. inherit
  4.     {NONE} SHARED_EJSON
  5.  
  6. create
  7.     make
  8.  
  9. feature
  10.  
  11.     make is
  12.         local
  13.             jfc: JSON_FOOBAR_CONVERTER
  14.         do
  15.             create jfc.make
  16.             json.add_converter (jfc)
  17.         end
  18.  
  19.     some_interesting_feature is
  20.         local
  21.             s: STRING
  22.             f: FOOBAR
  23.             jv: JSON_VALUE
  24.         do
  25.             -- Assume we got a string containing JSON from somewhere (the network or from a database)
  26.             s := from_somewhere
  27.             f := json.object_from_json (s, "FOOBAR")
  28.             ...
  29.             -- Assume we have manipulated the FOOBAR object and want to serialize it to a string with JSON
  30.             jv = json.value (f)
  31.             s := jv.representation
  32.         end

The eJSON visitor pattern

TBD.

The eJSON file reader class

Compiling and running the eJSON tests

From JSON Number to Eiffel (OBSOLETE)

This example require the extension of JSON_NUMBER class with the following features

  1. class JSON_NUMBER
  2. ...
  3. feature -- Conversion
  4.  
  5.         to_double: REAL_64
  6.                         -- double value
  7.                 require
  8.                         is_double: is_double
  9.  
  10.         to_integer: INTEGER_32
  11.                         -- integer value
  12.                 require
  13.                         is_integer: is_integer
  14.  
  15. feature -- Status
  16.  
  17.        
  18.         is_double: BOOLEAN
  19.                         -- Does item represent an DOUBLE?
  20.  
  21.         is_integer: BOOLEAN
  22.                         -- Does item represent an INTEGER?
  23.  
  24. ...
  1.     from_json_number_to_eiffel_number is
  2.             -- From JSON numbers to Eiffel Numbers
  3.             local
  4.                 value:JSON_VALUE
  5.                 value_number:JSON_NUMBER
  6.  
  7.                 l_int:INTEGER
  8.                 l_real:DOUBLE
  9.                 parse_json:JSON_PARSER
  10.             do
  11.                 create parse_json.make_parser ("10")
  12.                     -- Parsing a number from a String (or a File or other source)
  13.  
  14.                 value:=parse_json.parse_json
  15.                 print ("%N From JSON to Eiffel Integer Number%N")
  16.  
  17.                 if parse_json.is_parsed then
  18.                     value_number ?= value
  19.                     if value_number /= void then
  20.                       if value_number.is_integer then
  21.                           l_int:= value_number.to_integer
  22.                           print ("%N El valor integer es:" + l_int.out)
  23.                       end
  24.                     end
  25.                 end
  26.  
  27.                 print ("%N From JSON to Eiffel Real Number%N")
  28.  
  29.                 create parse_json.make_parser ("10.23")
  30.                     -- Parsing a number from a String (or a File or other source)
  31.  
  32.                 value:=parse_json.parse_json
  33.  
  34.                 if parse_json.is_parsed then
  35.                     value_number ?= value
  36.                     if value_number /= void then
  37.                       if value_number.is_double then
  38.                           l_real:= value_number.to_double
  39.                           print ("%N El valor real es:" + l_real.out)
  40.                         end
  41.                     end
  42.                 end
  43.             end

Output


  1.  From JSON to Eiffel Integer Number
  2.  The integer value is:10
  3.  
  4.  From JSON to Eiffel Real Number
  5.  The real value is:10.23

From JSON Boolean to Eiffel (OBSOLETE)

  1.     from_json_boolean_to_eiffel is
  2.                 -- From JSON Boolean to Eiffel
  3.                 local
  4.                         value:JSON_VALUE
  5.                 value_boolean:JSON_BOOLEAN
  6.  
  7.                 l_bool:BOOLEAN
  8.                 parse_json:JSON_PARSER
  9.             do
  10.                 print("%N From  JSON Boolean to Eiffel %N")
  11.                 create parse_json.make_parser ("true")
  12.                         -- Parsing a Boolean from a string
  13.                 value :=parse_json.parse_json
  14.                 if parse_json.is_parsed then
  15.                         value_boolean ?=value
  16.                         print ("%NThe Boolean value is: " +value_boolean.item.out + "%N")
  17.                 end
  18.             end
Output


  1. From  JSON Boolean to Eiffel
  2. The Boolean value is: True

From JSON String to Eiffel (OBSOLETE)

  1. from_json_string_to_eiffel is
  2.                 -- From JSON String to Eiffel
  3.         local
  4.                 value:JSON_VALUE
  5.                 value_string:JSON_STRING
  6.                 parse_json:JSON_PARSER
  7.  
  8.          do
  9.                 print ("%N From JSON String to Eiffel%N")
  10.                 create parse_json.make_parser ("%"abcd%"")
  11.                 value := parse_json.parse_json
  12.  
  13.                 if parse_json.is_parsed then
  14.                         value_string ?= value
  15.                         print ("%N The String value is:"+ value_string.item +"%N")
  16.                 end
  17.          end
Output


  1. From JSON String to Eiffel
  2. The String value is:abcd