diff --git a/library/gobo/converters/json_ds_hash_table_converter.e b/library/gobo/converters/json_ds_hash_table_converter.e new file mode 100644 index 00000000..8cd9cce1 --- /dev/null +++ b/library/gobo/converters/json_ds_hash_table_converter.e @@ -0,0 +1,81 @@ +indexing + description: "A JSON converter for DS_HASH_TABLE [ANY, HASHABLE]" + author: "Paul Cohen" + date: "$Date: $" + revision: "$Revision: $" + file: "$HeadURL: $" + +class JSON_DS_HASH_TABLE_CONVERTER + +inherit + JSON_CONVERTER + +create + make + +feature {NONE} -- Initialization + + make is + do + create object.make (0) + end + +feature -- Access + + value: JSON_OBJECT + + object: DS_HASH_TABLE [ANY, HASHABLE] + +feature -- Conversion + + from_json (j: like value): like object is + local + keys: ARRAY [JSON_STRING] + i: INTEGER + h: HASHABLE + a: ANY + do + keys := j.current_keys + create Result.make (keys.count) + from + i := 1 + until + i > keys.count + loop + h ?= json.object (keys [i], void) + check h /= Void end + a := json.object (j.item (keys [i]), Void) + Result.put (a, h) + i := i + 1 + end + end + + to_json (o: like object): like value is + local + c: DS_HASH_TABLE_CURSOR [ANY, HASHABLE] + js: JSON_STRING + jv: JSON_VALUE + failed: BOOLEAN + do + create Result.make + from + c := o.new_cursor + c.start + until + c.after + loop + create js.make_json (c.key.out) + jv := json.value (c.item) + if jv /= Void then + Result.put (jv, js) + else + failed := True + end + c.forth + end + if failed then + Result := Void + end + end + +end -- class JSON_DS_HASH_TABLE_CONVERTER \ No newline at end of file diff --git a/library/gobo/converters/json_ds_linked_list_converter.e b/library/gobo/converters/json_ds_linked_list_converter.e new file mode 100644 index 00000000..c2a1a160 --- /dev/null +++ b/library/gobo/converters/json_ds_linked_list_converter.e @@ -0,0 +1,62 @@ +indexing + description: "A JSON converter for DS_LINKED_LIST [ANY]" + author: "Paul Cohen" + date: "$Date: $" + revision: "$Revision: $" + file: "$HeadURL: $" + +class JSON_DS_LINKED_LIST_CONVERTER + +inherit + JSON_CONVERTER + +create + make + +feature {NONE} -- Initialization + + make is + do + create object.make + end + +feature -- Access + + value: JSON_ARRAY + + object: DS_LINKED_LIST [ANY] + +feature -- Conversion + + from_json (j: like value): like object is + local + i: INTEGER + do + create Result.make + from + i := 1 + until + i > j.count + loop + Result.put_last (json.object (j [i], Void)) + i := i + 1 + end + end + + to_json (o: like object): like value is + local + c: DS_LIST_CURSOR [ANY] + do + create Result.make_array + from + c := o.new_cursor + c.start + until + c.after + loop + Result.add (json.value (c.item)) + c.forth + end + end + +end -- class JSON_DS_LINKED_LIST_CONVERTER \ No newline at end of file diff --git a/library/gobo/shared_gobo_ejson.e b/library/gobo/shared_gobo_ejson.e new file mode 100644 index 00000000..62bbfb72 --- /dev/null +++ b/library/gobo/shared_gobo_ejson.e @@ -0,0 +1,32 @@ +indexing + description: "[ + Shared factory class for creating JSON objects. Maps JSON + objects to Gobo DS_HASH_TABLEs and JSON arrays to Gobo + DS_LINKED_LISTs. Use non-conforming inheritance from this + class to ensure that your classes share the same + JSON_FACTORY instance. + ]" + author: "Paul Cohen" + date: "$Date$" + revision: "$Revision$" + file: "$HeadURL: $" + +class SHARED_GOBO_EJSON + +feature + + json: EJSON is + -- A shared EJSON instance with default converters for + -- DS_LINKED_LIST [ANY] and DS_HASH_TABLE [ANY, HASHABLE] + local + jllc: JSON_DS_LINKED_LIST_CONVERTER + jhtc: JSON_DS_HASH_TABLE_CONVERTER + once + create Result + create jllc.make + Result.add_converter (jllc) + create jhtc.make + Result.add_converter (jhtc) + end + +end -- class SHARED_GOBO_EJSON \ No newline at end of file diff --git a/library/kernel/converters/json_converter.e b/library/kernel/converters/json_converter.e new file mode 100644 index 00000000..2e8ff9c3 --- /dev/null +++ b/library/kernel/converters/json_converter.e @@ -0,0 +1,41 @@ +indexing + description: "A JSON converter" + author: "Paul Cohen" + date: "$Date: $" + revision: "$Revision: $" + file: "$HeadURL: $" + +deferred class JSON_CONVERTER + +inherit + SHARED_EJSON + +feature -- Access + + value: JSON_VALUE is + -- JSON value + deferred + end + + object: ANY is + -- Eiffel object + deferred + end + +feature -- Conversion + + from_json (j: like value): like object is + -- Convert from JSON value. Returns Void if unable to convert + deferred + end + + to_json (o: like object): like value is + -- Convert to JSON value + deferred + end + +invariant + has_eiffel_object: object /= Void -- An empty object must be created at creation time! + +end -- class JSON_CONVERTER + diff --git a/library/kernel/converters/json_hash_table_converter.e b/library/kernel/converters/json_hash_table_converter.e new file mode 100644 index 00000000..60b9fb57 --- /dev/null +++ b/library/kernel/converters/json_hash_table_converter.e @@ -0,0 +1,79 @@ +indexing + description: "A JSON converter for HASH_TABLE [ANY, HASHABLE]" + author: "Paul Cohen" + date: "$Date$" + revision: "$Revision$" + file: "$HeadURL: $" + +class JSON_HASH_TABLE_CONVERTER + +inherit + JSON_CONVERTER + +create + make + +feature {NONE} -- Initialization + + make is + do + create object.make (0) + end + +feature -- Access + + value: JSON_OBJECT + + object: HASH_TABLE [ANY, HASHABLE] + +feature -- Conversion + + from_json (j: like value): like object is + local + keys: ARRAY [JSON_STRING] + i: INTEGER + h: HASHABLE + a: ANY + do + keys := j.current_keys + create Result.make (keys.count) + from + i := 1 + until + i > keys.count + loop + h ?= json.object (keys [i], void) + check h /= Void end + a := json.object (j.item (keys [i]), Void) + Result.put (a, h) + i := i + 1 + end + end + + to_json (o: like object): like value is + local + js: JSON_STRING + jv: JSON_VALUE + failed: BOOLEAN + do + create Result.make + from + o.start + until + o.after + loop + create js.make_json (o.key_for_iteration.out) + jv := json.value (o.item_for_iteration) + if jv /= Void then + Result.put (jv, js) + else + failed := True + end + o.forth + end + if failed then + Result := Void + end + end + +end -- class JSON_HASH_TABLE_CONVERTER \ No newline at end of file diff --git a/library/kernel/converters/json_linked_list_converter.e b/library/kernel/converters/json_linked_list_converter.e new file mode 100644 index 00000000..0eec8cfc --- /dev/null +++ b/library/kernel/converters/json_linked_list_converter.e @@ -0,0 +1,61 @@ +indexing + description: "A JSON converter for LINKED_LIST [ANY]" + author: "Paul Cohen" + date: "$Date$" + revision: "$Revision$" + file: "$HeadURL: $" + +class JSON_LINKED_LIST_CONVERTER + +inherit + JSON_CONVERTER + +create + make + +feature {NONE} -- Initialization + + make is + do + create object.make + end + +feature -- Access + + value: JSON_ARRAY + + object: LINKED_LIST [ANY] + +feature -- Conversion + + from_json (j: like value): like object is + local + i: INTEGER + do + create Result.make + from + i := 1 + until + i > j.count + loop + Result.extend (json.object (j [i], Void)) + i := i + 1 + end + end + + to_json (o: like object): like value is + local + c: LINKED_LIST_CURSOR [ANY] + do + create Result.make_array + from + o.start + until + o.after + loop + Result.add (json.value (o.item)) + o.forth + end + end + +end -- class JSON_LINKED_LIST_CONVERTER \ No newline at end of file diff --git a/library/kernel/ejson.e b/library/kernel/ejson.e new file mode 100644 index 00000000..200d3f76 --- /dev/null +++ b/library/kernel/ejson.e @@ -0,0 +1,345 @@ +indexing + 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} KL_EXCEPTIONS + +feature -- Access + + value (an_object: ?ANY): JSON_VALUE is + -- JSON value from Eiffel object. Raises an "eJSON exception" if + -- unable to convert value. + local + b: BOOLEAN + i: INTEGER + i8: INTEGER_8 + i16: INTEGER_16 + i32: INTEGER_32 + i64: INTEGER_64 + n8: NATURAL_8 + n16: NATURAL_16 + n32: NATURAL_32 + n64: NATURAL_64 + r32: REAL_32 + r64: REAL_64 + a: ARRAY [ANY] + c: CHARACTER + s8: STRING_8 + ucs: UC_STRING + ja: JSON_ARRAY + jc: JSON_CONVERTER + 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 an_object.conforms_to (a_boolean) then + b ?= an_object + create {JSON_BOOLEAN} Result.make_boolean (b) + elseif an_object.conforms_to (an_integer_8) then + i8 ?= an_object + create {JSON_NUMBER} Result.make_integer (i8) + elseif an_object.conforms_to (an_integer_16) then + i16 ?= an_object + create {JSON_NUMBER} Result.make_integer (i16) + elseif an_object.conforms_to (an_integer_32) then + i32 ?= an_object + create {JSON_NUMBER} Result.make_integer (i32) + elseif an_object.conforms_to (an_integer_64) then + i64 ?= an_object + create {JSON_NUMBER} Result.make_integer (i64) + elseif an_object.conforms_to (a_natural_8) then + n8 ?= an_object + create {JSON_NUMBER} Result.make_natural (n8) + elseif an_object.conforms_to (a_natural_16) then + n16 ?= an_object + create {JSON_NUMBER} Result.make_natural (n16) + elseif an_object.conforms_to (a_natural_32) then + n32 ?= an_object + create {JSON_NUMBER} Result.make_natural (n32) + elseif an_object.conforms_to (a_natural_64) then + n64 ?= an_object + create {JSON_NUMBER} Result.make_natural (n64) + elseif an_object.conforms_to (a_real_32) then + r32 ?= an_object + create {JSON_NUMBER} Result.make_real (r32) + elseif an_object.conforms_to (a_real_64) then + r64 ?= an_object + create {JSON_NUMBER} Result.make_real (r64) + elseif an_object.conforms_to (an_array) then + a ?= an_object + create ja.make_array + from + i := a.lower + until + i > a.upper + loop + ja.add (value (a @ i)) + i := i + 1 + end + Result := ja + elseif an_object.conforms_to (a_character) then + c ?= an_object + create {JSON_STRING} Result.make_json (c.out) + elseif an_object.conforms_to (a_uc_string) then + ucs ?= an_object + create {JSON_STRING} Result.make_json (ucs.to_utf8) + elseif an_object.conforms_to (a_string_8) then + s8 ?= an_object + create {JSON_STRING} Result.make_json (s8) + end + + if Result = Void then + -- Now check the converters + jc := converter_for (an_object) + if jc /= Void then + Result := jc.to_json (an_object) + else + raise (exception_failed_to_convert_to_json (an_object)) + end + end + end + + object (a_value: JSON_VALUE; base_class: ?STRING): ANY is + -- 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. + require + a_value_not_void: a_value /= Void + local + jc: JSON_CONVERTER + jb: JSON_BOOLEAN + jn: JSON_NUMBER + js: JSON_STRING + ja: JSON_ARRAY + jo: JSON_OBJECT + i: INTEGER + ll: LINKED_LIST [ANY] + t: HASH_TABLE [ANY, UC_STRING] + keys: ARRAY [JSON_STRING] + ucs: UC_STRING + do + if base_class = Void then + if a_value.generator.is_equal ("JSON_NULL") then + Result := Void + elseif a_value.generator.is_equal ("JSON_BOOLEAN") then + jb ?= a_value + check jb /= Void end + Result := jb.item + elseif a_value.generator.is_equal ("JSON_NUMBER") then + jn ?= a_value + check jn /= Void end + 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 a_value.generator.is_equal ("JSON_STRING") then + js ?= a_value + check js /= Void end + create ucs.make_from_string (js.item) + Result := ucs + elseif a_value.generator.is_equal ("JSON_ARRAY") then + ja ?= a_value + check ja /= Void end + from + create ll.make () + i := 1 + until + i > ja.count + loop + ll.extend (object (ja [i], Void)) + i := i + 1 + end + Result := ll + elseif a_value.generator.is_equal ("JSON_OBJECT") then + jo ?= a_value + check jo /= Void end + keys := jo.current_keys + create t.make (keys.count) + from + i := keys.lower + until + i > keys.upper + loop + ucs ?= object (keys [i], Void) + check ucs /= Void end + t.put (object (jo.item (keys [i]), Void), ucs) + i := i + 1 + end + Result := t + end + else + if converters.has (base_class) then + jc := converters @ base_class + Result := jc.from_json (a_value) + else + raise (exception_failed_to_convert_to_eiffel (a_value, base_class)) + end + end + end + + object_from_json (json: STRING; base_class: ?STRING): ANY is + -- 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: JSON_VALUE + do + json_parser.set_representation (json) + jv := json_parser.parse + Result := object (jv, base_class) + end + + converter_for (an_object: ANY): JSON_CONVERTER is + -- Converter for objects. Returns Void if none found. + require + an_object_not_void: an_object /= Void + do + if converters.has (an_object.generator) then + Result := converters @ an_object.generator + end + end + + json_reference (s: STRING): JSON_OBJECT is + -- 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: DS_LIST [STRING]): JSON_ARRAY is + -- 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: DS_LIST_CURSOR [STRING] + do + create Result.make_array + from + c := l.new_cursor + c.start + until + c.after + loop + Result.add (json_reference (c.item)) + c.forth + end + end + +feature -- Change + + add_converter (jc: JSON_CONVERTER) is + -- 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: DS_HASH_TABLE [JSON_CONVERTER, STRING] is + -- Converters hashed by generator (base class) + once + create Result.make (10) + end + +feature {NONE} -- Implementation (Exceptions) + + exception_prefix: STRING is "eJSON exception: " + + exception_failed_to_convert_to_eiffel (a_value: JSON_VALUE; base_class: ?STRING): STRING is + -- 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: ?ANY): STRING is + -- Exception message for failing to convert `a' to a JSON_VALUE. + do + Result := exception_prefix + "Failed to convert Eiffel object to a JSON_VALUE: " + an_object.generator + end + +feature {NONE} -- Implementation (JSON parser) + + json_parser: JSON_PARSER is + 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] is + once + Result := <<>> + end + + a_character: CHARACTER + + a_string_8: STRING_8 is + once + Result := "" + end + + a_uc_string: UC_STRING is + once + create Result.make_from_string ("") + end + +end -- class EJSON \ No newline at end of file diff --git a/library/kernel/shared_ejson.e b/library/kernel/shared_ejson.e new file mode 100644 index 00000000..4d6ef7f5 --- /dev/null +++ b/library/kernel/shared_ejson.e @@ -0,0 +1,32 @@ +indexing + description: "[ + Shared factory class for creating JSON objects. Maps JSON + objects to ELKS HASH_TABLEs and JSON arrays to ELKS + LINKED_LISTs. Use non-conforming inheritance from this + class to ensure that your classes share the same + JSON_FACTORY instance. + ]" + author: "Paul Cohen" + date: "$Date: $" + revision: "$Revision: $" + file: "$HeadURL: $" + +class SHARED_EJSON + +feature + + json: EJSON is + -- A shared EJSON instance with default converters for + -- DS_LINKED_LIST [ANY] and DS_HASH_TABLE [ANY, HASHABLE] + local + jllc: JSON_LINKED_LIST_CONVERTER + jhtc: JSON_HASH_TABLE_CONVERTER + once + create Result + create jllc.make + Result.add_converter (jllc) + create jhtc.make + Result.add_converter (jhtc) + end + +end -- class SHARED_EJSON \ No newline at end of file