Minor changes + cosmetics

Added conversion from STRING to JSON_STRING to help users.
This commit is contained in:
jfiat
2011-10-07 12:19:27 +00:00
parent 05b4bd90f5
commit 3739909e43
3 changed files with 392 additions and 433 deletions

View File

@@ -1,312 +1,268 @@
note note
description: "Core factory class for creating JSON objects and corresponding Eiffel objects." description: "Core factory class for creating JSON objects and corresponding Eiffel objects."
author: "Paul Cohen" author: "Paul Cohen"
date: "$Date: $" date: "$Date: $"
revision: "$Revision: $" revision: "$Revision: $"
file: "$HeadURL: $" file: "$HeadURL: $"
class EJSON class EJSON
inherit inherit
{NONE} EXCEPTIONS EXCEPTIONS
feature -- Access feature -- Access
value (an_object: detachable ANY): detachable JSON_VALUE value (an_object: detachable ANY): detachable JSON_VALUE
-- JSON value from Eiffel object. Raises an "eJSON exception" if -- JSON value from Eiffel object. Raises an "eJSON exception" if
-- unable to convert value. -- unable to convert value.
local local
i: INTEGER i: INTEGER
ja: JSON_ARRAY ja: JSON_ARRAY
do do
-- Try to convert from basic Eiffel types. Note that we check with -- Try to convert from basic Eiffel types. Note that we check with
-- `conforms_to' since the client may have subclassed the base class -- `conforms_to' since the client may have subclassed the base class
-- that these basic types are derived from. -- that these basic types are derived from.
if an_object = Void then if an_object = Void then
create {JSON_NULL} Result create {JSON_NULL} Result
elseif attached {BOOLEAN} an_object as b then elseif attached {BOOLEAN} an_object as b then
create {JSON_BOOLEAN} Result.make_boolean (b) create {JSON_BOOLEAN} Result.make_boolean (b)
elseif attached {INTEGER_8} an_object as i8 then elseif attached {INTEGER_8} an_object as i8 then
create {JSON_NUMBER} Result.make_integer (i8) create {JSON_NUMBER} Result.make_integer (i8)
elseif attached {INTEGER_16} an_object as i16 then elseif attached {INTEGER_16} an_object as i16 then
create {JSON_NUMBER} Result.make_integer (i16) create {JSON_NUMBER} Result.make_integer (i16)
elseif attached {INTEGER_32} an_object as i32 then elseif attached {INTEGER_32} an_object as i32 then
create {JSON_NUMBER} Result.make_integer (i32) create {JSON_NUMBER} Result.make_integer (i32)
elseif attached {INTEGER_64} an_object as i64 then elseif attached {INTEGER_64} an_object as i64 then
create {JSON_NUMBER} Result.make_integer (i64) create {JSON_NUMBER} Result.make_integer (i64)
elseif attached {NATURAL_8} an_object as n8 then elseif attached {NATURAL_8} an_object as n8 then
create {JSON_NUMBER} Result.make_natural (n8) create {JSON_NUMBER} Result.make_natural (n8)
elseif attached {NATURAL_16} an_object as n16 then elseif attached {NATURAL_16} an_object as n16 then
create {JSON_NUMBER} Result.make_natural (n16) create {JSON_NUMBER} Result.make_natural (n16)
elseif attached {NATURAL_32} an_object as n32 then elseif attached {NATURAL_32} an_object as n32 then
create {JSON_NUMBER} Result.make_natural (n32) create {JSON_NUMBER} Result.make_natural (n32)
elseif attached {NATURAL_64} an_object as n64 then elseif attached {NATURAL_64} an_object as n64 then
create {JSON_NUMBER} Result.make_natural (n64) create {JSON_NUMBER} Result.make_natural (n64)
elseif attached {REAL_32} an_object as r32 then elseif attached {REAL_32} an_object as r32 then
create {JSON_NUMBER} Result.make_real (r32) create {JSON_NUMBER} Result.make_real (r32)
elseif attached {REAL_64} an_object as r64 then elseif attached {REAL_64} an_object as r64 then
create {JSON_NUMBER} Result.make_real (r64) create {JSON_NUMBER} Result.make_real (r64)
elseif attached {ARRAY [detachable ANY]} an_object as a then elseif attached {ARRAY [detachable ANY]} an_object as a then
create ja.make_array create ja.make_array
from from
i := a.lower i := a.lower
until until
i > a.upper i > a.upper
loop loop
if attached value (a @ i) as v then if attached value (a @ i) as v then
ja.add (v) ja.add (v)
else else
check value_attached: False end check value_attached: False end
end end
i := i + 1 i := i + 1
end end
Result := ja Result := ja
elseif attached {CHARACTER_8} an_object as c8 then elseif attached {CHARACTER_8} an_object as c8 then
create {JSON_STRING} Result.make_json (c8.out) create {JSON_STRING} Result.make_json (c8.out)
elseif attached {CHARACTER_32} an_object as c32 then elseif attached {CHARACTER_32} an_object as c32 then
create {JSON_STRING} Result.make_json (c32.out) create {JSON_STRING} Result.make_json (c32.out)
elseif attached {STRING_8} an_object as s8 then elseif attached {STRING_8} an_object as s8 then
create {JSON_STRING} Result.make_json (s8) create {JSON_STRING} Result.make_json (s8)
elseif attached {STRING_32} an_object as s32 then elseif attached {STRING_32} an_object as s32 then
create {JSON_STRING} Result.make_json (s32.as_string_8) -- FIXME: need correct convertion/encoding here ... create {JSON_STRING} Result.make_json (s32.as_string_8) -- FIXME: need correct convertion/encoding here ...
end end
if Result = Void then if Result = Void then
-- Now check the converters -- Now check the converters
if an_object /= Void and then attached converter_for (an_object) as jc then if an_object /= Void and then attached converter_for (an_object) as jc then
Result := jc.to_json (an_object) Result := jc.to_json (an_object)
else else
raise (exception_failed_to_convert_to_json (an_object)) raise (exception_failed_to_convert_to_json (an_object))
end end
end end
end end
object (a_value: detachable JSON_VALUE; base_class: detachable STRING): detachable ANY object (a_value: detachable JSON_VALUE; base_class: detachable STRING): detachable ANY
-- Eiffel object from JSON value. If `base_class' /= Void an eiffel -- Eiffel object from JSON value. If `base_class' /= Void an eiffel
-- object based on `base_class' will be returned. Raises an "eJSON -- object based on `base_class' will be returned. Raises an "eJSON
-- exception" if unable to convert value. -- exception" if unable to convert value.
local local
i: INTEGER i: INTEGER
ll: LINKED_LIST [detachable ANY] ll: LINKED_LIST [detachable ANY]
t: HASH_TABLE [detachable ANY, STRING_GENERAL] t: HASH_TABLE [detachable ANY, STRING_GENERAL]
keys: ARRAY [JSON_STRING] keys: ARRAY [JSON_STRING]
s32: STRING_32 do
s: detachable STRING_GENERAL if a_value = Void then
do Result := Void
if a_value = Void then else
Result := Void if base_class = Void then
else if a_value = Void then
if base_class = Void then Result := Void
if a_value = Void then elseif attached {JSON_NULL} a_value then
Result := Void Result := Void
elseif attached {JSON_NULL} a_value then elseif attached {JSON_BOOLEAN} a_value as jb then
Result := Void Result := jb.item
elseif attached {JSON_BOOLEAN} a_value as jb then elseif attached {JSON_NUMBER} a_value as jn then
Result := jb.item if jn.item.is_integer_8 then
elseif attached {JSON_NUMBER} a_value as jn then Result := jn.item.to_integer_8
if jn.item.is_integer_8 then elseif jn.item.is_integer_16 then
Result := jn.item.to_integer_8 Result := jn.item.to_integer_16
elseif jn.item.is_integer_16 then elseif jn.item.is_integer_32 then
Result := jn.item.to_integer_16 Result := jn.item.to_integer_32
elseif jn.item.is_integer_32 then elseif jn.item.is_integer_64 then
Result := jn.item.to_integer_32 Result := jn.item.to_integer_64
elseif jn.item.is_integer_64 then elseif jn.item.is_natural_64 then
Result := jn.item.to_integer_64 Result := jn.item.to_natural_64
elseif jn.item.is_natural_64 then elseif jn.item.is_double then
Result := jn.item.to_natural_64 Result := jn.item.to_double
elseif jn.item.is_double then end
Result := jn.item.to_double elseif attached {JSON_STRING} a_value as js then
end create {STRING_32} Result.make_from_string (js.item)
elseif attached {JSON_STRING} a_value as js then elseif attached {JSON_ARRAY} a_value as ja then
create s32.make_from_string (js.item) from
Result := s32 create ll.make
elseif attached {JSON_ARRAY} a_value as ja then i := 1
from until
create ll.make i > ja.count
i := 1 loop
until ll.extend (object (ja [i], Void))
i > ja.count i := i + 1
loop end
ll.extend (object (ja [i], Void)) Result := ll
i := i + 1 elseif attached {JSON_OBJECT} a_value as jo then
end keys := jo.current_keys
Result := ll create t.make (keys.count)
elseif attached {JSON_OBJECT} a_value as jo then from
keys := jo.current_keys i := keys.lower
create t.make (keys.count) until
from i > keys.upper
i := keys.lower loop
until if attached {STRING_GENERAL} object (keys [i], Void) as s then
i > keys.upper t.put (object (jo.item (keys [i]), Void), s)
loop end
s ?= object (keys [i], Void) i := i + 1
check s /= Void end end
t.put (object (jo.item (keys [i]), Void), s) Result := t
i := i + 1 end
end else
Result := t if converters.has_key (base_class) and then attached converters.found_item as jc then
end Result := jc.from_json (a_value)
else else
if converters.has_key (base_class) and then attached converters.found_item as jc then raise (exception_failed_to_convert_to_eiffel (a_value, base_class))
Result := jc.from_json (a_value) end
else end
raise (exception_failed_to_convert_to_eiffel (a_value, base_class)) end
end end
end
end object_from_json (json: STRING; base_class: detachable STRING): detachable ANY
end -- Eiffel object from JSON representation. If `base_class' /= Void an
-- Eiffel object based on `base_class' will be returned. Raises an
object_from_json (json: STRING; base_class: detachable STRING): detachable ANY -- "eJSON exception" if unable to convert value.
-- Eiffel object from JSON representation. If `base_class' /= Void an require
-- Eiffel object based on `base_class' will be returned. Raises an json_not_void: json /= Void
-- "eJSON exception" if unable to convert value. local
require jv: detachable JSON_VALUE
json_not_void: json /= Void do
local json_parser.set_representation (json)
jv: detachable JSON_VALUE jv := json_parser.parse
do if jv /= Void then
json_parser.set_representation (json) Result := object (jv, base_class)
jv := json_parser.parse end
if jv /= Void then end
Result := object (jv, base_class)
end converter_for (an_object: ANY): detachable JSON_CONVERTER
end -- Converter for objects. Returns Void if none found.
require
converter_for (an_object: ANY): detachable JSON_CONVERTER an_object_not_void: an_object /= Void
-- Converter for objects. Returns Void if none found. do
require if converters.has_key (an_object.generator) then
an_object_not_void: an_object /= Void Result := converters.found_item
do end
if converters.has_key (an_object.generator) then end
Result := converters.found_item
end json_reference (s: STRING): JSON_OBJECT
end -- A JSON (Dojo style) reference object using `s' as the
-- reference value. The caller is responsable for ensuring
json_reference (s: STRING): JSON_OBJECT -- the validity of `s' as a json reference.
-- A JSON (Dojo style) reference object using `s' as the require
-- reference value. The caller is responsable for ensuring s_not_void: s /= Void
-- the validity of `s' as a json reference. local
require js_key, js_value: JSON_STRING
s_not_void: s /= Void do
local create Result.make
js_key, js_value: JSON_STRING create js_key.make_json ("$ref")
do create js_value.make_json (s)
create Result.make Result.put (js_value, js_key)
create js_key.make_json ("$ref") end
create js_value.make_json (s)
Result.put (js_value, js_key) json_references (l: LIST [STRING]): JSON_ARRAY
end -- A JSON array of JSON (Dojo style) reference objects using the
-- strings in `l' as reference values. The caller is responsable
json_references (l: LIST [STRING]): JSON_ARRAY -- for ensuring the validity of all strings in `l' as json
-- A JSON array of JSON (Dojo style) reference objects using the -- references.
-- strings in `l' as reference values. The caller is responsable require
-- for ensuring the validity of all strings in `l' as json l_not_void: l /= Void
-- references. local
require c: ITERATION_CURSOR [STRING]
l_not_void: l /= Void do
local create Result.make_array
c: ITERATION_CURSOR [STRING] from
do c := l.new_cursor
create Result.make_array until
from c.after
c := l.new_cursor loop
until Result.add (json_reference (c.item))
c.after c.forth
loop end
Result.add (json_reference (c.item)) end
c.forth
end feature -- Change
end
add_converter (jc: JSON_CONVERTER)
feature -- Change -- Add the converter `jc'.
require
add_converter (jc: JSON_CONVERTER) jc_not_void: jc /= Void
-- Add the converter `jc'. do
require converters.force (jc, jc.object.generator)
jc_not_void: jc /= Void ensure
do has_converter: converter_for (jc.object) /= Void
converters.force (jc, jc.object.generator) end
ensure
has_converter: converter_for (jc.object) /= Void feature {NONE} -- Implementation
end
converters: HASH_TABLE [JSON_CONVERTER, STRING]
feature {NONE} -- Implementation -- Converters hashed by generator (base class)
once
converters: HASH_TABLE [JSON_CONVERTER, STRING] create Result.make (10)
-- Converters hashed by generator (base class) end
once
create Result.make (10) feature {NONE} -- Implementation (Exceptions)
end
exception_prefix: STRING = "eJSON exception: "
feature {NONE} -- Implementation (Exceptions)
exception_failed_to_convert_to_eiffel (a_value: JSON_VALUE; base_class: detachable STRING): STRING
exception_prefix: STRING = "eJSON exception: " -- Exception message for failing to convert a JSON_VALUE to an instance of `a'.
do
exception_failed_to_convert_to_eiffel (a_value: JSON_VALUE; base_class: detachable STRING): STRING Result := exception_prefix + "Failed to convert JSON_VALUE to an Eiffel object: " + a_value.generator
-- Exception message for failing to convert a JSON_VALUE to an instance of `a'. if base_class /= Void then
do Result.append (" -> " + base_class)
Result := exception_prefix + "Failed to convert JSON_VALUE to an Eiffel object: " + a_value.generator end
if base_class /= Void then end
Result.append (" -> " + base_class)
end exception_failed_to_convert_to_json (an_object: detachable ANY): STRING
end -- Exception message for failing to convert `a' to a JSON_VALUE.
do
exception_failed_to_convert_to_json (an_object: detachable ANY): STRING Result := exception_prefix + "Failed to convert Eiffel object to a JSON_VALUE"
-- Exception message for failing to convert `a' to a JSON_VALUE. if an_object /= Void then
do Result := ": " + an_object.generator
Result := exception_prefix + "Failed to convert Eiffel object to a JSON_VALUE" end
if an_object /= Void then end
Result := ": " + an_object.generator
end feature {NONE} -- Implementation (JSON parser)
end
json_parser: JSON_PARSER
feature {NONE} -- Implementation (JSON parser) once
create Result.make_parser ("")
json_parser: JSON_PARSER end
once
create Result.make_parser ("") end -- class EJSON
end
feature {NONE} -- Implementation (Basic Eiffel objects)
a_boolean: BOOLEAN
an_integer_8: INTEGER_8
an_integer_16: INTEGER_16
an_integer_32: INTEGER_32
an_integer_64: INTEGER_64
a_natural_8: NATURAL_8
a_natural_16: NATURAL_16
a_natural_32: NATURAL_32
a_natural_64: NATURAL_64
a_real_32: REAL_32
a_real_64: REAL_64
an_array: ARRAY [ANY]
once
Result := <<>>
end
a_character: CHARACTER
a_string_8: STRING_8
once
Result := ""
end
a_string_32: STRING_32
once
Result := {STRING_32} ""
end
end -- class EJSON

View File

@@ -24,6 +24,9 @@ inherit
create create
make_json make_json
convert
make_json ({STRING})
feature {NONE} -- Initialization feature {NONE} -- Initialization
make_json (an_item: STRING) make_json (an_item: STRING)
@@ -45,7 +48,7 @@ feature -- Access
Result.append (item) Result.append (item)
Result.append_character ('%"') Result.append_character ('%"')
end end
feature -- Visitor pattern feature -- Visitor pattern
accept (a_visitor: JSON_VISITOR) accept (a_visitor: JSON_VISITOR)
@@ -92,7 +95,7 @@ feature -- Status report
feature {NONE} -- Implementation feature {NONE} -- Implementation
escaped_json_string (s: STRING): STRING escaped_json_string (s: READABLE_STRING_8): STRING
-- JSON string with '"' and '\' characters escaped -- JSON string with '"' and '\' characters escaped
require require
s_not_void: s /= Void s_not_void: s /= Void
@@ -101,7 +104,7 @@ feature {NONE} -- Implementation
Result.replace_substring_all ("\", "\\") Result.replace_substring_all ("\", "\\")
Result.replace_substring_all ("%"", "\%"") Result.replace_substring_all ("%"", "\%"")
end end
invariant invariant
value_not_void: item /= Void value_not_void: item /= Void

View File

@@ -1,118 +1,118 @@
note note
description: "Objects that ..." description: "Objects that ..."
author: "jvelilla" author: "jvelilla"
date: "2008/08/24" date: "2008/08/24"
revision: "0.1" revision: "0.1"
class class
JSON_READER JSON_READER
create create
make make
feature {NONE} -- Initialization feature {NONE} -- Initialization
make (a_json: STRING) make (a_json: STRING)
-- Initialize Reader -- Initialize Reader
do do
set_representation (a_json) set_representation (a_json)
end end
feature -- Commands feature -- Commands
set_representation (a_json: STRING) set_representation (a_json: STRING)
-- Set `representation'. -- Set `representation'.
do do
a_json.left_adjust a_json.left_adjust
a_json.right_adjust a_json.right_adjust
representation := a_json representation := a_json
index := 1 index := 1
end end
read: CHARACTER read: CHARACTER
-- Read character -- Read character
do do
if not representation.is_empty then if not representation.is_empty then
Result := representation.item (index) Result := representation.item (index)
end end
end end
next next
-- Move to next index -- Move to next index
require require
has_more_elements: has_next has_more_elements: has_next
do do
index := index + 1 index := index + 1
ensure ensure
incremented: old index + 1 = index incremented: old index + 1 = index
end end
previous previous
-- Move to previous index -- Move to previous index
require require
not_is_first: has_previous not_is_first: has_previous
do do
index := index - 1 index := index - 1
ensure ensure
incremented: old index - 1 = index incremented: old index - 1 = index
end end
skip_white_spaces skip_white_spaces
-- Remove white spaces -- Remove white spaces
local local
c: like actual c: like actual
do do
from from
c := actual c := actual
until until
(c /= ' ' and c /= '%N' and c /= '%R' and c /= '%U' and c /= '%T' ) or not has_next (c /= ' ' and c /= '%N' and c /= '%R' and c /= '%U' and c /= '%T' ) or not has_next
loop loop
next next
c := actual c := actual
end end
end end
json_substring (start_index, end_index: INTEGER_32): STRING json_substring (start_index, end_index: INTEGER_32): STRING
-- JSON representation between `start_index' and `end_index' -- JSON representation between `start_index' and `end_index'
do do
Result := representation.substring (start_index, end_index) Result := representation.substring (start_index, end_index)
end end
feature -- Status report feature -- Status report
has_next: BOOLEAN has_next: BOOLEAN
-- Has a next character? -- Has a next character?
do do
Result := index <= representation.count Result := index <= representation.count
end end
has_previous: BOOLEAN has_previous: BOOLEAN
-- Has a previous character? -- Has a previous character?
do do
Result := index >= 1 Result := index >= 1
end end
feature -- Access feature -- Access
representation: STRING representation: STRING
-- Serialized representation of the original JSON string -- Serialized representation of the original JSON string
feature {NONE} -- Implementation feature {NONE} -- Implementation
actual: CHARACTER actual: CHARACTER
-- Current character or '%U' if none -- Current character or '%U' if none
do do
if index > representation.count then if index > representation.count then
Result := '%U' Result := '%U'
else else
Result := representation.item (index) Result := representation.item (index)
end end
end end
index: INTEGER index: INTEGER
-- Actual index -- Actual index
invariant invariant
representation_not_void: representation /= Void representation_not_void: representation /= Void
end end