From 3739909e43f1b6003fe822b46c9e5eb34a965e11 Mon Sep 17 00:00:00 2001 From: jfiat Date: Fri, 7 Oct 2011 12:19:27 +0000 Subject: [PATCH] Minor changes + cosmetics Added conversion from STRING to JSON_STRING to help users. --- library/kernel/ejson.e | 580 +++++++++++++-------------- library/kernel/json_string.e | 9 +- library/kernel/scanner/json_reader.e | 236 +++++------ 3 files changed, 392 insertions(+), 433 deletions(-) diff --git a/library/kernel/ejson.e b/library/kernel/ejson.e index a22ec4ef..9a828b17 100644 --- a/library/kernel/ejson.e +++ b/library/kernel/ejson.e @@ -1,312 +1,268 @@ -note - description: "Core factory class for creating JSON objects and corresponding Eiffel objects." - author: "Paul Cohen" - date: "$Date: $" - revision: "$Revision: $" - file: "$HeadURL: $" - -class EJSON - -inherit - {NONE} EXCEPTIONS - -feature -- Access - - value (an_object: detachable ANY): detachable JSON_VALUE - -- JSON value from Eiffel object. Raises an "eJSON exception" if - -- unable to convert value. - local - i: INTEGER - ja: JSON_ARRAY - do - -- Try to convert from basic Eiffel types. Note that we check with - -- `conforms_to' since the client may have subclassed the base class - -- that these basic types are derived from. - if an_object = Void then - create {JSON_NULL} Result - elseif attached {BOOLEAN} an_object as b then - create {JSON_BOOLEAN} Result.make_boolean (b) - elseif attached {INTEGER_8} an_object as i8 then - create {JSON_NUMBER} Result.make_integer (i8) - elseif attached {INTEGER_16} an_object as i16 then - create {JSON_NUMBER} Result.make_integer (i16) - elseif attached {INTEGER_32} an_object as i32 then - create {JSON_NUMBER} Result.make_integer (i32) - elseif attached {INTEGER_64} an_object as i64 then - create {JSON_NUMBER} Result.make_integer (i64) - elseif attached {NATURAL_8} an_object as n8 then - create {JSON_NUMBER} Result.make_natural (n8) - elseif attached {NATURAL_16} an_object as n16 then - create {JSON_NUMBER} Result.make_natural (n16) - elseif attached {NATURAL_32} an_object as n32 then - create {JSON_NUMBER} Result.make_natural (n32) - elseif attached {NATURAL_64} an_object as n64 then - create {JSON_NUMBER} Result.make_natural (n64) - elseif attached {REAL_32} an_object as r32 then - create {JSON_NUMBER} Result.make_real (r32) - elseif attached {REAL_64} an_object as r64 then - create {JSON_NUMBER} Result.make_real (r64) - elseif attached {ARRAY [detachable ANY]} an_object as a then - create ja.make_array - from - i := a.lower - until - i > a.upper - loop - if attached value (a @ i) as v then - ja.add (v) - else - check value_attached: False end - end - i := i + 1 - end - Result := ja - elseif attached {CHARACTER_8} an_object as c8 then - create {JSON_STRING} Result.make_json (c8.out) - elseif attached {CHARACTER_32} an_object as c32 then - create {JSON_STRING} Result.make_json (c32.out) - - elseif attached {STRING_8} an_object as s8 then - create {JSON_STRING} Result.make_json (s8) - 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 ... - end - - if Result = Void then - -- Now check the converters - if an_object /= Void and then attached converter_for (an_object) as jc then - Result := jc.to_json (an_object) - else - raise (exception_failed_to_convert_to_json (an_object)) - end - end - end - - object (a_value: detachable JSON_VALUE; base_class: detachable STRING): detachable ANY - -- Eiffel object from JSON value. If `base_class' /= Void an eiffel - -- object based on `base_class' will be returned. Raises an "eJSON - -- exception" if unable to convert value. - local - i: INTEGER - ll: LINKED_LIST [detachable ANY] - t: HASH_TABLE [detachable ANY, STRING_GENERAL] - keys: ARRAY [JSON_STRING] - s32: STRING_32 - s: detachable STRING_GENERAL - do - if a_value = Void then - Result := Void - else - if base_class = Void then - if a_value = Void then - Result := Void - elseif attached {JSON_NULL} a_value then - Result := Void - elseif attached {JSON_BOOLEAN} a_value as jb then - Result := jb.item - elseif attached {JSON_NUMBER} a_value as jn then - if jn.item.is_integer_8 then - Result := jn.item.to_integer_8 - elseif jn.item.is_integer_16 then - Result := jn.item.to_integer_16 - elseif jn.item.is_integer_32 then - Result := jn.item.to_integer_32 - elseif jn.item.is_integer_64 then - Result := jn.item.to_integer_64 - elseif jn.item.is_natural_64 then - Result := jn.item.to_natural_64 - elseif jn.item.is_double then - Result := jn.item.to_double - end - elseif attached {JSON_STRING} a_value as js then - create s32.make_from_string (js.item) - Result := s32 - elseif attached {JSON_ARRAY} a_value as ja then - from - create ll.make - i := 1 - until - i > ja.count - loop - ll.extend (object (ja [i], Void)) - i := i + 1 - end - Result := ll - elseif attached {JSON_OBJECT} a_value as jo then - keys := jo.current_keys - create t.make (keys.count) - from - i := keys.lower - until - i > keys.upper - loop - s ?= object (keys [i], Void) - check s /= Void end - t.put (object (jo.item (keys [i]), Void), s) - i := i + 1 - end - Result := t - end - else - if converters.has_key (base_class) and then attached converters.found_item as jc then - Result := jc.from_json (a_value) - else - raise (exception_failed_to_convert_to_eiffel (a_value, base_class)) - end - end - end - end - - object_from_json (json: STRING; base_class: detachable STRING): detachable ANY - -- Eiffel object from JSON representation. If `base_class' /= Void an - -- Eiffel object based on `base_class' will be returned. Raises an - -- "eJSON exception" if unable to convert value. - require - json_not_void: json /= Void - local - jv: detachable JSON_VALUE - do - json_parser.set_representation (json) - jv := json_parser.parse - if jv /= Void then - Result := object (jv, base_class) - end - end - - converter_for (an_object: ANY): detachable JSON_CONVERTER - -- Converter for objects. Returns Void if none found. - require - an_object_not_void: an_object /= Void - do - if converters.has_key (an_object.generator) then - Result := converters.found_item - end - end - - json_reference (s: STRING): JSON_OBJECT - -- A JSON (Dojo style) reference object using `s' as the - -- reference value. The caller is responsable for ensuring - -- the validity of `s' as a json reference. - require - s_not_void: s /= Void - local - js_key, js_value: JSON_STRING - do - create Result.make - create js_key.make_json ("$ref") - create js_value.make_json (s) - Result.put (js_value, js_key) - end - - json_references (l: LIST [STRING]): JSON_ARRAY - -- A JSON array of JSON (Dojo style) reference objects using the - -- strings in `l' as reference values. The caller is responsable - -- for ensuring the validity of all strings in `l' as json - -- references. - require - l_not_void: l /= Void - local - c: ITERATION_CURSOR [STRING] - do - create Result.make_array - from - c := l.new_cursor - until - c.after - loop - Result.add (json_reference (c.item)) - c.forth - end - end - -feature -- Change - - add_converter (jc: JSON_CONVERTER) - -- Add the converter `jc'. - require - jc_not_void: jc /= Void - do - converters.force (jc, jc.object.generator) - ensure - has_converter: converter_for (jc.object) /= Void - end - -feature {NONE} -- Implementation - - converters: HASH_TABLE [JSON_CONVERTER, STRING] - -- Converters hashed by generator (base class) - once - create Result.make (10) - end - -feature {NONE} -- Implementation (Exceptions) - - exception_prefix: STRING = "eJSON exception: " - - exception_failed_to_convert_to_eiffel (a_value: JSON_VALUE; base_class: detachable STRING): STRING - -- Exception message for failing to convert a JSON_VALUE to an instance of `a'. - do - Result := exception_prefix + "Failed to convert JSON_VALUE to an Eiffel object: " + a_value.generator - if base_class /= Void then - Result.append (" -> " + base_class) - end - end - - exception_failed_to_convert_to_json (an_object: detachable ANY): STRING - -- Exception message for failing to convert `a' to a JSON_VALUE. - do - Result := exception_prefix + "Failed to convert Eiffel object to a JSON_VALUE" - if an_object /= Void then - Result := ": " + an_object.generator - end - end - -feature {NONE} -- Implementation (JSON parser) - - json_parser: JSON_PARSER - once - create Result.make_parser ("") - 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 +note + description: "Core factory class for creating JSON objects and corresponding Eiffel objects." + author: "Paul Cohen" + date: "$Date: $" + revision: "$Revision: $" + file: "$HeadURL: $" + +class EJSON + +inherit + EXCEPTIONS + +feature -- Access + + value (an_object: detachable ANY): detachable JSON_VALUE + -- JSON value from Eiffel object. Raises an "eJSON exception" if + -- unable to convert value. + local + i: INTEGER + ja: JSON_ARRAY + do + -- Try to convert from basic Eiffel types. Note that we check with + -- `conforms_to' since the client may have subclassed the base class + -- that these basic types are derived from. + if an_object = Void then + create {JSON_NULL} Result + elseif attached {BOOLEAN} an_object as b then + create {JSON_BOOLEAN} Result.make_boolean (b) + elseif attached {INTEGER_8} an_object as i8 then + create {JSON_NUMBER} Result.make_integer (i8) + elseif attached {INTEGER_16} an_object as i16 then + create {JSON_NUMBER} Result.make_integer (i16) + elseif attached {INTEGER_32} an_object as i32 then + create {JSON_NUMBER} Result.make_integer (i32) + elseif attached {INTEGER_64} an_object as i64 then + create {JSON_NUMBER} Result.make_integer (i64) + elseif attached {NATURAL_8} an_object as n8 then + create {JSON_NUMBER} Result.make_natural (n8) + elseif attached {NATURAL_16} an_object as n16 then + create {JSON_NUMBER} Result.make_natural (n16) + elseif attached {NATURAL_32} an_object as n32 then + create {JSON_NUMBER} Result.make_natural (n32) + elseif attached {NATURAL_64} an_object as n64 then + create {JSON_NUMBER} Result.make_natural (n64) + elseif attached {REAL_32} an_object as r32 then + create {JSON_NUMBER} Result.make_real (r32) + elseif attached {REAL_64} an_object as r64 then + create {JSON_NUMBER} Result.make_real (r64) + elseif attached {ARRAY [detachable ANY]} an_object as a then + create ja.make_array + from + i := a.lower + until + i > a.upper + loop + if attached value (a @ i) as v then + ja.add (v) + else + check value_attached: False end + end + i := i + 1 + end + Result := ja + elseif attached {CHARACTER_8} an_object as c8 then + create {JSON_STRING} Result.make_json (c8.out) + elseif attached {CHARACTER_32} an_object as c32 then + create {JSON_STRING} Result.make_json (c32.out) + + elseif attached {STRING_8} an_object as s8 then + create {JSON_STRING} Result.make_json (s8) + 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 ... + end + + if Result = Void then + -- Now check the converters + if an_object /= Void and then attached converter_for (an_object) as jc then + Result := jc.to_json (an_object) + else + raise (exception_failed_to_convert_to_json (an_object)) + end + end + end + + object (a_value: detachable JSON_VALUE; base_class: detachable STRING): detachable ANY + -- Eiffel object from JSON value. If `base_class' /= Void an eiffel + -- object based on `base_class' will be returned. Raises an "eJSON + -- exception" if unable to convert value. + local + i: INTEGER + ll: LINKED_LIST [detachable ANY] + t: HASH_TABLE [detachable ANY, STRING_GENERAL] + keys: ARRAY [JSON_STRING] + do + if a_value = Void then + Result := Void + else + if base_class = Void then + if a_value = Void then + Result := Void + elseif attached {JSON_NULL} a_value then + Result := Void + elseif attached {JSON_BOOLEAN} a_value as jb then + Result := jb.item + elseif attached {JSON_NUMBER} a_value as jn then + if jn.item.is_integer_8 then + Result := jn.item.to_integer_8 + elseif jn.item.is_integer_16 then + Result := jn.item.to_integer_16 + elseif jn.item.is_integer_32 then + Result := jn.item.to_integer_32 + elseif jn.item.is_integer_64 then + Result := jn.item.to_integer_64 + elseif jn.item.is_natural_64 then + Result := jn.item.to_natural_64 + elseif jn.item.is_double then + Result := jn.item.to_double + end + elseif attached {JSON_STRING} a_value as js then + create {STRING_32} Result.make_from_string (js.item) + elseif attached {JSON_ARRAY} a_value as ja then + from + create ll.make + i := 1 + until + i > ja.count + loop + ll.extend (object (ja [i], Void)) + i := i + 1 + end + Result := ll + elseif attached {JSON_OBJECT} a_value as jo then + keys := jo.current_keys + create t.make (keys.count) + from + i := keys.lower + until + i > keys.upper + loop + if attached {STRING_GENERAL} object (keys [i], Void) as s then + t.put (object (jo.item (keys [i]), Void), s) + end + i := i + 1 + end + Result := t + end + else + if converters.has_key (base_class) and then attached converters.found_item as jc then + Result := jc.from_json (a_value) + else + raise (exception_failed_to_convert_to_eiffel (a_value, base_class)) + end + end + end + end + + object_from_json (json: STRING; base_class: detachable STRING): detachable ANY + -- Eiffel object from JSON representation. If `base_class' /= Void an + -- Eiffel object based on `base_class' will be returned. Raises an + -- "eJSON exception" if unable to convert value. + require + json_not_void: json /= Void + local + jv: detachable JSON_VALUE + do + json_parser.set_representation (json) + jv := json_parser.parse + if jv /= Void then + Result := object (jv, base_class) + end + end + + converter_for (an_object: ANY): detachable JSON_CONVERTER + -- Converter for objects. Returns Void if none found. + require + an_object_not_void: an_object /= Void + do + if converters.has_key (an_object.generator) then + Result := converters.found_item + end + end + + json_reference (s: STRING): JSON_OBJECT + -- A JSON (Dojo style) reference object using `s' as the + -- reference value. The caller is responsable for ensuring + -- the validity of `s' as a json reference. + require + s_not_void: s /= Void + local + js_key, js_value: JSON_STRING + do + create Result.make + create js_key.make_json ("$ref") + create js_value.make_json (s) + Result.put (js_value, js_key) + end + + json_references (l: LIST [STRING]): JSON_ARRAY + -- A JSON array of JSON (Dojo style) reference objects using the + -- strings in `l' as reference values. The caller is responsable + -- for ensuring the validity of all strings in `l' as json + -- references. + require + l_not_void: l /= Void + local + c: ITERATION_CURSOR [STRING] + do + create Result.make_array + from + c := l.new_cursor + until + c.after + loop + Result.add (json_reference (c.item)) + c.forth + end + end + +feature -- Change + + add_converter (jc: JSON_CONVERTER) + -- Add the converter `jc'. + require + jc_not_void: jc /= Void + do + converters.force (jc, jc.object.generator) + ensure + has_converter: converter_for (jc.object) /= Void + end + +feature {NONE} -- Implementation + + converters: HASH_TABLE [JSON_CONVERTER, STRING] + -- Converters hashed by generator (base class) + once + create Result.make (10) + end + +feature {NONE} -- Implementation (Exceptions) + + exception_prefix: STRING = "eJSON exception: " + + exception_failed_to_convert_to_eiffel (a_value: JSON_VALUE; base_class: detachable STRING): STRING + -- Exception message for failing to convert a JSON_VALUE to an instance of `a'. + do + Result := exception_prefix + "Failed to convert JSON_VALUE to an Eiffel object: " + a_value.generator + if base_class /= Void then + Result.append (" -> " + base_class) + end + end + + exception_failed_to_convert_to_json (an_object: detachable ANY): STRING + -- Exception message for failing to convert `a' to a JSON_VALUE. + do + Result := exception_prefix + "Failed to convert Eiffel object to a JSON_VALUE" + if an_object /= Void then + Result := ": " + an_object.generator + end + end + +feature {NONE} -- Implementation (JSON parser) + + json_parser: JSON_PARSER + once + create Result.make_parser ("") + end + +end -- class EJSON diff --git a/library/kernel/json_string.e b/library/kernel/json_string.e index 274776a1..b49ef7bc 100644 --- a/library/kernel/json_string.e +++ b/library/kernel/json_string.e @@ -24,6 +24,9 @@ inherit create make_json +convert + make_json ({STRING}) + feature {NONE} -- Initialization make_json (an_item: STRING) @@ -45,7 +48,7 @@ feature -- Access Result.append (item) Result.append_character ('%"') end - + feature -- Visitor pattern accept (a_visitor: JSON_VISITOR) @@ -92,7 +95,7 @@ feature -- Status report feature {NONE} -- Implementation - escaped_json_string (s: STRING): STRING + escaped_json_string (s: READABLE_STRING_8): STRING -- JSON string with '"' and '\' characters escaped require s_not_void: s /= Void @@ -101,7 +104,7 @@ feature {NONE} -- Implementation Result.replace_substring_all ("\", "\\") Result.replace_substring_all ("%"", "\%"") end - + invariant value_not_void: item /= Void diff --git a/library/kernel/scanner/json_reader.e b/library/kernel/scanner/json_reader.e index 12a074ea..112857bd 100644 --- a/library/kernel/scanner/json_reader.e +++ b/library/kernel/scanner/json_reader.e @@ -1,118 +1,118 @@ -note - description: "Objects that ..." - author: "jvelilla" - date: "2008/08/24" - revision: "0.1" - -class - JSON_READER - -create - make - -feature {NONE} -- Initialization - - make (a_json: STRING) - -- Initialize Reader - do - set_representation (a_json) - end - -feature -- Commands - - set_representation (a_json: STRING) - -- Set `representation'. - do - a_json.left_adjust - a_json.right_adjust - representation := a_json - index := 1 - end - - read: CHARACTER - -- Read character - do - if not representation.is_empty then - Result := representation.item (index) - end - end - - next - -- Move to next index - require - has_more_elements: has_next - do - index := index + 1 - ensure - incremented: old index + 1 = index - end - - previous - -- Move to previous index - require - not_is_first: has_previous - do - index := index - 1 - ensure - incremented: old index - 1 = index - end - - skip_white_spaces - -- Remove white spaces - local - c: like actual - do - from - c := actual - until - (c /= ' ' and c /= '%N' and c /= '%R' and c /= '%U' and c /= '%T' ) or not has_next - loop - next - c := actual - end - end - - json_substring (start_index, end_index: INTEGER_32): STRING - -- JSON representation between `start_index' and `end_index' - do - Result := representation.substring (start_index, end_index) - end - -feature -- Status report - - has_next: BOOLEAN - -- Has a next character? - do - Result := index <= representation.count - end - - has_previous: BOOLEAN - -- Has a previous character? - do - Result := index >= 1 - end - -feature -- Access - - representation: STRING - -- Serialized representation of the original JSON string - -feature {NONE} -- Implementation - - actual: CHARACTER - -- Current character or '%U' if none - do - if index > representation.count then - Result := '%U' - else - Result := representation.item (index) - end - end - - index: INTEGER - -- Actual index - -invariant - representation_not_void: representation /= Void - -end +note + description: "Objects that ..." + author: "jvelilla" + date: "2008/08/24" + revision: "0.1" + +class + JSON_READER + +create + make + +feature {NONE} -- Initialization + + make (a_json: STRING) + -- Initialize Reader + do + set_representation (a_json) + end + +feature -- Commands + + set_representation (a_json: STRING) + -- Set `representation'. + do + a_json.left_adjust + a_json.right_adjust + representation := a_json + index := 1 + end + + read: CHARACTER + -- Read character + do + if not representation.is_empty then + Result := representation.item (index) + end + end + + next + -- Move to next index + require + has_more_elements: has_next + do + index := index + 1 + ensure + incremented: old index + 1 = index + end + + previous + -- Move to previous index + require + not_is_first: has_previous + do + index := index - 1 + ensure + incremented: old index - 1 = index + end + + skip_white_spaces + -- Remove white spaces + local + c: like actual + do + from + c := actual + until + (c /= ' ' and c /= '%N' and c /= '%R' and c /= '%U' and c /= '%T' ) or not has_next + loop + next + c := actual + end + end + + json_substring (start_index, end_index: INTEGER_32): STRING + -- JSON representation between `start_index' and `end_index' + do + Result := representation.substring (start_index, end_index) + end + +feature -- Status report + + has_next: BOOLEAN + -- Has a next character? + do + Result := index <= representation.count + end + + has_previous: BOOLEAN + -- Has a previous character? + do + Result := index >= 1 + end + +feature -- Access + + representation: STRING + -- Serialized representation of the original JSON string + +feature {NONE} -- Implementation + + actual: CHARACTER + -- Current character or '%U' if none + do + if index > representation.count then + Result := '%U' + else + Result := representation.item (index) + end + end + + index: INTEGER + -- Actual index + +invariant + representation_not_void: representation /= Void + +end