diff --git a/.gitignore b/.gitignore index a1ff1e8..62bee11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ *.swp EIFGENs +TESTGEN +ejson_test.h +ejson_test*.c +ejson_test.sh +ejson_test*.o +ejson_test diff --git a/library/gobo/converters/json_ds_hash_table_converter.e b/library/gobo/converters/json_ds_hash_table_converter.e index 46cf0ff..7f2625d 100644 --- a/library/gobo/converters/json_ds_hash_table_converter.e +++ b/library/gobo/converters/json_ds_hash_table_converter.e @@ -34,9 +34,11 @@ feature -- Conversion i: INTEGER h: HASHABLE a: ANY + ucs: UC_STRING do keys := j.current_keys create Result.make (keys.count) + create ucs.make_empty from i := 1 until @@ -44,7 +46,13 @@ feature -- Conversion loop h ?= json.object (keys [i], void) check h /= Void end + if attached {STRING_32} h and attached Json.converter_for (ucs) then + h ?= json.object (keys [i], ucs.generator) + end a := json.object (j.item (keys [i]), Void) + if attached {STRING_32} a and attached Json.converter_for (ucs) then + a := json.object (j.item (keys [i]), ucs.generator) + end Result.put (a, h) i := i + 1 end diff --git a/library/gobo/converters/json_uc_string_converter.e b/library/gobo/converters/json_uc_string_converter.e new file mode 100644 index 0000000..25de837 --- /dev/null +++ b/library/gobo/converters/json_uc_string_converter.e @@ -0,0 +1,39 @@ +note + description: "JSON UC_STRING converter." + author: "Olivier Ligot" + +class + JSON_UC_STRING_CONVERTER + +inherit + JSON_CONVERTER + +create + make + +feature {NONE} -- Initialization + + make + do + create object.make (0) + end + +feature -- Access + + value: JSON_STRING + + object: UC_STRING + +feature -- Conversion + + from_json (j: like value): like object + do + create Result.make_from_string_general (j.unescaped_string_32) + end + + to_json (o: like object): like value + do + create Result.make_json_from_string_32 (o.as_string_32) + end + +end diff --git a/library/json-safe.ecf b/library/json-safe.ecf index b957513..5342859 100644 --- a/library/json-safe.ecf +++ b/library/json-safe.ecf @@ -14,15 +14,20 @@ - + + + + + + + + + + - ^/gobo$ - ^/kernel$ - ^/extras$ + .*/?gobo.* - - diff --git a/library/json.ecf b/library/json.ecf index 8d6dc61..e5f1d2a 100644 --- a/library/json.ecf +++ b/library/json.ecf @@ -14,15 +14,20 @@ - + + + + + + + + + + - ^/gobo$ - ^/kernel$ - ^/extras$ + .*/?gobo.* - - diff --git a/library/json_gobo_extension-portable.ecf b/library/json_gobo_extension-portable.ecf new file mode 100644 index 0000000..b038b95 --- /dev/null +++ b/library/json_gobo_extension-portable.ecf @@ -0,0 +1,27 @@ + + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + diff --git a/library/json_gobo_extension.ecf b/library/json_gobo_extension.ecf index dd5f9b7..700e8c6 100644 --- a/library/json_gobo_extension.ecf +++ b/library/json_gobo_extension.ecf @@ -9,8 +9,18 @@ - + + + + + + + + + + + diff --git a/library/kernel/converters/json_hash_table_converter.e b/library/kernel/converters/json_hash_table_converter.e index cf3f9cf..7b513b3 100644 --- a/library/kernel/converters/json_hash_table_converter.e +++ b/library/kernel/converters/json_hash_table_converter.e @@ -46,7 +46,7 @@ feature -- Conversion jv := j.item (keys [i]) if jv /= Void then a := json.object (jv, Void) - if a /= Void then + if a /= Void and h /= Void then Result.put (a, h) else check a_attached: a /= Void end diff --git a/test/getest/author.e b/test/getest/author.e new file mode 100644 index 0000000..f150365 --- /dev/null +++ b/test/getest/author.e @@ -0,0 +1,24 @@ +class AUTHOR + +create + make + +feature {NONE} -- Initialization + + make (a_name: UC_STRING) + do + set_name (a_name) + end + +feature -- Access + + name: UC_STRING + +feature -- Status setting + + set_name (a_name: UC_STRING) + do + name := a_name + end + +end -- class AUTHOR diff --git a/test/getest/book.e b/test/getest/book.e new file mode 100644 index 0000000..5681221 --- /dev/null +++ b/test/getest/book.e @@ -0,0 +1,40 @@ +class BOOK + +create + make + +feature {NONE} -- Initialization + + make (a_title: UC_STRING; an_author: AUTHOR; an_isbn: UC_STRING) + do + set_title (a_title) + set_author (an_author) + set_isbn (an_isbn) + end + +feature -- Access + + title: UC_STRING + + isbn: UC_STRING + + author: AUTHOR + +feature -- Status setting + + set_title (a_title: UC_STRING) + do + title := a_title + end + + set_author (an_author: AUTHOR) + do + author := an_author + end + + set_isbn (an_isbn: UC_STRING) + do + isbn := an_isbn + end + +end -- class BOOK diff --git a/test/getest/book_collection.e b/test/getest/book_collection.e new file mode 100644 index 0000000..1b6db0f --- /dev/null +++ b/test/getest/book_collection.e @@ -0,0 +1,82 @@ +class BOOK_COLLECTION + +create + make + +feature {NONE} -- Initialization + + make (a_name: UC_STRING) + do + set_name (a_name) + create book_index.make (10) + end + +feature -- Access + + name: UC_STRING + + books: DS_LIST [BOOK] + local + c: DS_HASH_TABLE_CURSOR [DS_LIST [BOOK], UC_STRING] + do + from + create {DS_LINKED_LIST [BOOK]} Result.make + c := book_index.new_cursor + c.start + until + c.after + loop + Result.append_last (c.item) + c.forth + end + end + + books_by_author (an_author: UC_STRING): DS_LIST [BOOK] + do + if book_index.has (an_author) then + Result := book_index @ an_author + else + create {DS_LINKED_LIST [BOOK]} Result.make + end + end + +feature -- Status setting + + set_name (a_name: UC_STRING) + do + name := a_name + end + + add_book (a_book: BOOK) + local + l: DS_LIST [BOOK] + do + if book_index.has (a_book.author.name) then + l := book_index @ a_book.author.name + else + create {DS_LINKED_LIST [BOOK]} l.make + book_index.put (l, a_book.author.name) + end + l.put_last (a_book) + end + + add_books (book_list: like books) + local + c: DS_LIST_CURSOR [BOOK] + do + from + c := book_list.new_cursor + c.start + until + c.after + loop + add_book (c.item) + c.forth + end + end + +feature {NONE} -- Implementation + + book_index: DS_HASH_TABLE [DS_LIST [BOOK], UC_STRING] + +end -- class BOOK_COLLECTION diff --git a/test/getest/ec_compile.bat b/test/getest/ec_compile.bat new file mode 100644 index 0000000..1d90e40 --- /dev/null +++ b/test/getest/ec_compile.bat @@ -0,0 +1,11 @@ +echo Compiling ejson_test (finalized) +ecb -finalize -c_compile -config ejson_test.ecf -batch -clean > NUL 2>&1 +IF %ERRORLEVEL% EQU -1 goto ERROR +copy EIFGENs\ejson_test\F_code\ejson_test.exe ejson_test.exe +goto EOF + +:ERROR +echo Error occurred during ejson_test compilation +goto EOF + +:EOF diff --git a/test/getest/ec_compile.sh b/test/getest/ec_compile.sh new file mode 100755 index 0000000..08dcd61 --- /dev/null +++ b/test/getest/ec_compile.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "ec -finalize -c_compile -config ejson_test.ecf > /dev/null 2>&1" +ec -finalize -c_compile -config ejson_test.ecf > /dev/null 2>&1 +cp EIFGENs/ejson_test/F_code/ejson_test . diff --git a/test/getest/ejson_test-win.cfg b/test/getest/ejson_test-win.cfg new file mode 100644 index 0000000..9c905c2 --- /dev/null +++ b/test/getest/ejson_test-win.cfg @@ -0,0 +1,17 @@ +-- Gobo test (getest) configuration file for eJSON + +test + ejson_test + +default + class ("TEST_[A-Z0-9_]*") + feature ("test_[a-z0-9_]*") + prefix ("X") + testgen ("TESTGEN") + compile ("ec_compile.bat") + execute ("ejson_test.exe") + +cluster + test_dir: "." + +end diff --git a/test/getest/ejson_test-win.ge b/test/getest/ejson_test-win.ge new file mode 100644 index 0000000..fcf91f4 --- /dev/null +++ b/test/getest/ejson_test-win.ge @@ -0,0 +1,16 @@ +-- Gobo test (getest) configuration file for eJSON + +test + ejson_test + +default + class ("TEST_[A-Z0-9_]*") + feature ("test_[a-z0-9_]*") + prefix ("X") + testgen ("TESTGEN") + compile ("ge_compile.bat") + +cluster + test_dir: "." + +end diff --git a/test/getest/ejson_test.cfg b/test/getest/ejson_test.cfg new file mode 100644 index 0000000..078526a --- /dev/null +++ b/test/getest/ejson_test.cfg @@ -0,0 +1,17 @@ +-- Gobo test (getest) configuration file for eJSON + +test + ejson_test + +default + class ("TEST_[A-Z0-9_]*") + feature ("test_[a-z0-9_]*") + prefix ("X") + testgen ("TESTGEN") + compile ("./ec_compile.sh") + execute ("./ejson_test") + +cluster + test_dir: "." + +end diff --git a/test/getest/ejson_test.ecf b/test/getest/ejson_test.ecf new file mode 100644 index 0000000..e8ad9f3 --- /dev/null +++ b/test/getest/ejson_test.ecf @@ -0,0 +1,30 @@ + + + + + + //.svn + /cvs$ + /EIFGENs$ + + + + + + + + + + + + + + + + + + + + diff --git a/test/getest/ejson_test.ge b/test/getest/ejson_test.ge new file mode 100644 index 0000000..f3b4f52 --- /dev/null +++ b/test/getest/ejson_test.ge @@ -0,0 +1,16 @@ +-- Gobo test (getest) configuration file for eJSON + +test + ejson_test + +default + class ("TEST_[A-Z0-9_]*") + feature ("test_[a-z0-9_]*") + prefix ("X") + testgen ("TESTGEN") + compile ("./ge_compile.sh") + +cluster + test_dir: "." + +end diff --git a/test/getest/ge_compile.bat b/test/getest/ge_compile.bat new file mode 100644 index 0000000..72436f9 --- /dev/null +++ b/test/getest/ge_compile.bat @@ -0,0 +1,3 @@ +echo Compiling with gec +set GOBO_EIFFEL=ge +gec.exe --finalize --catcall=no ejson_test.ecf diff --git a/test/getest/ge_compile.sh b/test/getest/ge_compile.sh new file mode 100755 index 0000000..b165bfd --- /dev/null +++ b/test/getest/ge_compile.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +echo "Compiling with gec" +GOBO_EIFFEL=ge gec --finalize --catcall=no ejson_test.ecf diff --git a/test/getest/json_author_converter.e b/test/getest/json_author_converter.e new file mode 100644 index 0000000..36f06cd --- /dev/null +++ b/test/getest/json_author_converter.e @@ -0,0 +1,55 @@ +note + description: "A JSON converter for AUTHOR" + author: "Paul Cohen" + date: "$Date$" + revision: "$Revision$" + +class JSON_AUTHOR_CONVERTER + +inherit + JSON_CONVERTER + +create + make + +feature {NONE} -- Initialization + + make + local + ucs: UC_STRING + do + create ucs.make_from_string ("") + create object.make (ucs) + end + +feature -- Access + + value: JSON_OBJECT + + object: AUTHOR + +feature -- Conversion + + from_json (j: like value): like object + local + ucs: UC_STRING + do + ucs ?= json.object (j.item (name_key), "UC_STRING") + check ucs /= Void end + create Result.make (ucs) + end + + to_json (o: like object): like value + do + create Result.make + Result.put (json.value (o.name), name_key) + end + +feature {NONE} -- Implementation + + name_key: JSON_STRING + once + create Result.make_json ("name") + end + +end -- class JSON_AUTHOR_CONVERTER diff --git a/test/getest/json_book_collection_converter.e b/test/getest/json_book_collection_converter.e new file mode 100644 index 0000000..c16ac6a --- /dev/null +++ b/test/getest/json_book_collection_converter.e @@ -0,0 +1,80 @@ +note + description: "A JSON converter for BOOK_COLLECTION" + author: "Paul Cohen" + date: "$Date$" + revision: "$Revision$" + +class JSON_BOOK_COLLECTION_CONVERTER + +inherit + JSON_CONVERTER + +create + make + +feature {NONE} -- Initialization + + make + local + ucs: UC_STRING + do + create ucs.make_from_string ("") + create object.make (ucs) + end + +feature -- Access + + value: JSON_OBJECT + + object: BOOK_COLLECTION + +feature -- Conversion + + from_json (j: like value): like object + local + ucs: UC_STRING + ll: DS_LINKED_LIST [BOOK] + b: BOOK + ja: JSON_ARRAY + i: INTEGER + do + ucs ?= json.object (j.item (name_key), "UC_STRING") + check ucs /= Void end + create Result.make (ucs) + ja ?= j.item (books_key) + check ja /= Void end + from + i := 1 + create ll.make + until + i > ja.count + loop + b ?= json.object (ja [i], "BOOK") + check b /= Void end + ll.put_last (b) + i := i + 1 + end + check ll /= Void end + Result.add_books (ll) + end + + to_json (o: like object): like value + do + create Result.make + Result.put (json.value (o.name), name_key) + Result.put (json.value (o.books), books_key) + end + +feature {NONE} -- Implementation + + name_key: JSON_STRING + once + create Result.make_json ("name") + end + + books_key: JSON_STRING + once + create Result.make_json ("books") + end + +end -- class JSON_BOOK_COLLECTION_CONVERTER diff --git a/test/getest/json_book_converter.e b/test/getest/json_book_converter.e new file mode 100644 index 0000000..9b63455 --- /dev/null +++ b/test/getest/json_book_converter.e @@ -0,0 +1,74 @@ +note + description: "A JSON converter for BOOK" + author: "Paul Cohen" + date: "$Date$" + revision: "$Revision$" + +class JSON_BOOK_CONVERTER + +inherit + JSON_CONVERTER + +create + make + +feature {NONE} -- Initialization + + make + local + ucs: UC_STRING + a: AUTHOR + do + create ucs.make_from_string ("") + create a.make (ucs) + create object.make (ucs, a, ucs) + end + +feature -- Access + + value: JSON_OBJECT + + object: BOOK + +feature -- Conversion + + from_json (j: like value): like object + local + ucs1, ucs2: UC_STRING + a: AUTHOR + do + ucs1 ?= json.object (j.item (title_key), "UC_STRING") + check ucs1 /= Void end + ucs2 ?= json.object (j.item (isbn_key), "UC_STRING") + check ucs2 /= Void end + a ?= json.object (j.item (author_key), "AUTHOR") + check a /= Void end + create Result.make (ucs1, a, ucs2) + end + + to_json (o: like object): like value + do + create Result.make + Result.put (json.value (o.title), title_key) + Result.put (json.value (o.isbn), isbn_key) + Result.put (json.value (o.author), author_key) + end + +feature {NONE} -- Implementation + + title_key: JSON_STRING + once + create Result.make_json ("title") + end + + isbn_key: JSON_STRING + once + create Result.make_json ("isbn") + end + + author_key: JSON_STRING + once + create Result.make_json ("author") + end + +end -- class JSON_BOOK_CONVERTER diff --git a/test/getest/readme.txt b/test/getest/readme.txt new file mode 100644 index 0000000..d874160 --- /dev/null +++ b/test/getest/readme.txt @@ -0,0 +1,36 @@ +Gobo ecf files +============== + +To get the Gobo ecf files do as follows: + +1. Create a directory for the custum Eiffel libraries + +2. Define the environment variable EIFFEL_LIBRARY to point to the newly created directory + +3. Clone the Git repository https://github.com/oligot/gobo-ecf in the newly created directory + +EiffelStudio +============ + +To compile and run the test program using EiffelStudio do as follows: + +1. Make sure you have a compiled version of getest in your PATH. + +2. In this directory, run the command: + + $ getest --verbose ejson_test.cfg + +Note: on Windows, you should use ejson_test-win.cfg + +gec +=== + +To compile and run the test program using gec do as follows: + +1. Make sure you have a compiled version of getest in your PATH. + +2. In this directory, run the command: + + $ getest --verbose ejson_test.ge + +Note: on Windows, you should use ejson_test-win.ge diff --git a/test/getest/test_ds.e b/test/getest/test_ds.e new file mode 100644 index 0000000..6883306 --- /dev/null +++ b/test/getest/test_ds.e @@ -0,0 +1,72 @@ +class TEST_DS + +inherit + SHARED_GOBO_EJSON + + TS_TEST_CASE + +create + make_default + +feature {NONE} -- Initialization + + make + -- Create test object. + do + end + +feature -- Test + + test_ds_linked_list_converter + local + jc: JSON_DS_LINKED_LIST_CONVERTER + l: DS_LINKED_LIST [STRING] + l2: DS_LINKED_LIST [ANY] + s: STRING + jv: JSON_VALUE + do + create jc.make + json.add_converter (jc) + create l.make + s := "foo" + l.put_last (s) + s := "bar" + l.put_last (s) + jv := json.value (l) + assert ("jv /= Void", jv /= Void) + s := jv.representation + l2 ?= json.object (jv, "DS_LINKED_LIST") + assert ("l2 /= Void", l2 /= Void) + end + + test_ds_hash_table_converter + local + tc: JSON_DS_HASH_TABLE_CONVERTER + t: DS_HASH_TABLE [STRING, STRING] + t2: DS_HASH_TABLE [ANY, HASHABLE] + s: STRING + ucs_key, ucs_value: UC_STRING + jv: JSON_VALUE + do + create tc.make + json.add_converter (tc) + json.add_converter (create {JSON_UC_STRING_CONVERTER}.make) + create t.make (2) + t.put ("foo", "1") + t.put ("bar", "2") + jv := json.value (t) + assert ("jv /= Void", jv /= Void) + s := jv.representation + t2 ?= json.object (jv, "DS_HASH_TABLE") + assert ("t2 /= Void", t2 /= Void) + create ucs_key.make_from_string ("1") + ucs_value ?= t2 @ ucs_key + assert ("ucs_value /= Void", ucs_value /= Void) + assert ("ucs_value.string.is_equal (%"foo%")", ucs_value.string.is_equal ("foo")) + create ucs_key.make_from_string ("2") + ucs_value ?= t2 @ ucs_key + assert ("ucs_value /= Void", ucs_value /= Void) + assert ("ucs_value.string.is_equal (%"bar%")", ucs_value.string.is_equal ("bar")) + end + +end -- class TEST_DS diff --git a/test/getest/test_json_core.e b/test/getest/test_json_core.e new file mode 100644 index 0000000..591ca2e --- /dev/null +++ b/test/getest/test_json_core.e @@ -0,0 +1,790 @@ +class TEST_JSON_CORE + +inherit + TS_TEST_CASE + SHARED_EJSON + KL_SHARED_EIFFEL_COMPILER + +create + make_default + +feature {NONE} -- Initialization + + make + -- Create test object. + do + end + +feature -- Test + + test_json_number_and_integer + local + i: INTEGER + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + i := 42 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_integer (i) + assert ("jn.representation.same_string (%"42%")", jn.representation.same_string ("42")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_NUMBER} json.value (i) as l_jn then + assert ("l_jn.representation.same_string (%"42%")", jn.representation.same_string ("42")) + else + assert ("json.value (i) is a JSON_NUMBER", False) + end + + -- JSON representation-> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- we know it is INTEGER_8 since the value is 42 + jrep := "42" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {INTEGER_8} json.object (jn, Void) as l_i8 then + assert ("l_i8 = 42", l_i8 = 42) + else + assert ("json.object (jn, Void) is a INTEGER_8", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_integer_8 + local + i8: INTEGER_8 + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + i8 := 42 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_integer (i8) + assert ("jn.representation.same_string (%"42%")", jn.representation.same_string ("42")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_NUMBER} json.value (i8) as l_jn then + assert ("l_jn.representation.same_string (%"42%")", jn.representation.same_string ("42")) + else + assert ("json.value (i8) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- we know it is INTEGER_8 since the value is 42 + jrep := "42" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {INTEGER_8} json.object (jn, Void) as l_i8 then + assert ("l_i8 = 42", l_i8 = 42) + else + assert ("json.object (jn, Void) is a INTEGER_8", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_integer_16 + local + i16: INTEGER_16 + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + i16 := 300 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_integer (i16) + assert ("jn.representation.same_string (%"300%")", jn.representation.same_string ("300")) + -- Eiffel value -> JSON with factory + if attached {JSON_NUMBER} json.value (i16) as l_jn then + assert ("l_jn.representation.same_string (%"300%")", l_jn.representation.same_string ("300")) + else + assert ("json.value (i16) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- we know it is INTEGER_16 since the value is 300 + jrep := "300" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {INTEGER_16} json.object (jn, Void) as l_i16 then + assert ("l_i16 = 300", l_i16 = 300) + else + assert ("json.object (jn, Void) is a INTEGER_16", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_integer_32 + local + i32: INTEGER_32 + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + i32 := 100000 + -- Eiffel value -> JSON representation -> JSON value + create jn.make_integer (i32) + assert ("jn.representation.same_string (%"100000%")", jn.representation.same_string ("100000")) + -- Eiffel value -> JSON representation -> JSON value with factory + if attached {JSON_NUMBER} json.value (i32) as l_jn then + assert ("l_jn.representation.same_string (%"100000%")", l_jn.representation.same_string ("100000")) + else + assert ("json.value (i32) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- we know it is INTEGER_32 since the value is 100000 + jrep := "100000" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {INTEGER_32} json.object (jn, Void) as l_i32 then + assert ("l_i32 = 100000", l_i32 = 100000) + else + assert ("json.object (jn, Void) is a INTEGER_32", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_integer_64 + local + i64: INTEGER_64 + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + i64 := 42949672960 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_integer (i64) + assert ("jn.representation.same_string (%"42949672960%")", jn.representation.same_string ("42949672960")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_NUMBER} json.value (i64) as l_jn then + assert ("l_jn.representation.same_string (%"42949672960%")", l_jn.representation.same_string ("42949672960")) + else + assert ("json.value (i64) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- we know it is INTEGER_32 since the value is 42949672960 + jrep := "42949672960" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {INTEGER_64} json.object (jn, Void) as l_i64 then + assert ("l_i64 = 42949672960", l_i64 = 42949672960) + else + assert ("json.object (jn, Void) is a INTEGER_64", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_natural_8 + local + n8: NATURAL_8 + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + n8 := 200 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_natural (n8) + assert ("jn.representation.same_string (%"200%")", jn.representation.same_string ("200")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_NUMBER} json.value (n8) as l_jn then + assert ("l_jn.representation.same_string (%"200%")", l_jn.representation.same_string ("200")) + else + assert ("json.value (n8) is a JSON_NUMBER}", False) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- we know it is INTEGER_16 since the value is 200 + jrep := "200" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {INTEGER_16} json.object (jn, Void) as i16 then + assert ("i16 = 200", i16 = 200) + else + assert ("json.object (jn, Void) is an INTEGER_16", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_natural_16 + local + n16: NATURAL_16 + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + n16 := 32768 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_natural (n16) + assert ("jn.representation.same_string (%"32768%")", jn.representation.same_string ("32768")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_NUMBER} json.value (n16) as l_jn then + assert ("l_jn.representation.same_string (%"32768%")", l_jn.representation.same_string ("32768")) + else + assert ("json.value (n16) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- we know it is INTEGER_32 since the value is 32768 + jrep := "32768" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {INTEGER_32} json.object (jn, Void) as i32 then + assert ("i32 = 32768", i32 = 32768) + else + assert ("json.object (jn, Void) is a INTEGER_32", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_natural_32 + local + n32: NATURAL_32 + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + n32 := 2147483648 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_natural (n32) + assert ("jn.representation.same_string (%"2147483648%")", jn.representation.same_string ("2147483648")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached json.value (n32) as l_jn then + assert ("l_jn.representation.same_string (%"2147483648%")", l_jn.representation.same_string ("2147483648")) + else + assert ("json.value (n32) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- we know it is INTEGER_64 since the value is 2147483648 + jrep := "2147483648" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {INTEGER_64} json.object (jn, Void) as i64 then + assert ("i64 = 2147483648", i64 = 2147483648) + else + assert ("json.object (jn, Void) is a INTEGER_64", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_large_integers + local + jrep: STRING + n64: NATURAL_64 + jn: JSON_NUMBER + parser: JSON_PARSER + do + n64 := 9223372036854775808 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_natural (n64) + assert ("jn.representation.same_string (%"9223372036854775808%")", jn.representation.same_string ("9223372036854775808")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_NUMBER} json.value (n64) as l_jn then + assert ("l_jn.representation.same_string (%"9223372036854775808%")", l_jn.representation.same_string ("9223372036854775808")) + else + assert ("json.value (n64) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- we know it is INTEGER_32 since the value is 42949672960 + jrep := "9223372036854775808" -- 1 higher than largest positive number that can be represented by INTEGER 64 + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {NATURAL_64} json.object (jn, Void) as l_n64 then + assert ("l_n64 = 9223372036854775808", l_n64 = 9223372036854775808) + else + assert ("json.object (jn, Void) is a NATURAL_64", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_eiffel_real + local + r: REAL + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + r := 3.14 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_real (r) + assert ("jn.representation.same_string (%"3.1400001049041748%")", jn.representation.same_string ("3.1400001049041748")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_NUMBER} json.value (r) as l_jn then + assert ("l_jn.representation.same_string (%"3.1400001049041748%")", l_jn.representation.same_string ("3.1400001049041748")) + else + assert ("json.value (r) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will always return a REAL_64 if the value + -- of the JSON number is a floating point number + jrep := "3.14" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {REAL_64} json.object (jn, Void) as r64 then + assert ("3.14 <= r64 and r64 <= 3.141", 3.14 <= r64 and r64 <= 3.141) + else + assert ("json.object (jn, Void) is a REAL_64", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_eiffel_real_32 + local + r32: REAL_32 + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + r32 := 3.14 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_real (r32) + assert ("jn.representation.same_string (%"3.1400001049041748%")", jn.representation.same_string ("3.1400001049041748")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_NUMBER} json.value (r32) as l_jn then + assert ("l_jn.representation.same_string (%"3.1400001049041748%")", l_jn.representation.same_string ("3.1400001049041748")) + else + assert ("json.value (r32) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + jrep := "3.1400001049041748" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {REAL_64} json.object (l_jn, Void) as r64 then + assert ("r64 = 3.1400001049041748", r64 = 3.1400001049041748) + else + assert ("json.object (l_jn, Void) is a REAL_64", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_number_and_eiffel_real_64 + local + r64: REAL_64 + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + r64 := 3.1415926535897931 + -- Eiffel value -> JSON value -> JSON representation + create jn.make_real (r64) + assert ("jn.representation.same_string (%"3.1415926535897931%")", jn.representation.same_string ("3.1415926535897931")) + + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_NUMBER} json.value (r64) as l_jn then + assert ("l_jn.representation.same_string (%"3.1415926535897931%")", l_jn.representation.same_string ("3.1415926535897931")) + else + assert ("json.value (r64) is a JSON_NUMBER", False) + end + + -- JSON representation -> JSON value -> Eiffel value + jrep := "3.1415926535897931" + create parser.make_parser (jrep) + if attached {JSON_NUMBER} parser.parse as l_jn then + if attached {REAL_64} json.object (jn, Void) as l_r64 then + assert ("l_r64 = 3.1415926535897931", l_r64 = 3.1415926535897931) + else + assert ("json.object (jn, Void) is a REAL_64", False) + end + else + assert ("parser.parse is a JSON_NUMBER", False) + end + end + + test_json_boolean + local + parser: JSON_PARSER + jb: JSON_BOOLEAN + b: BOOLEAN + do + -- Eiffel value -> JSON value -> JSON representation + b := True + create jb.make_boolean (b) + assert ("jb.representation.is_equal (%"true%")", jb.representation.is_equal ("true")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_BOOLEAN} json.value (b) as l_jb then + assert ("l_jb.representation.same_string (%"true%")", l_jb.representation.same_string ("true")) + else + assert ("l_jb /= Void", False) + end + + -- JSON representation -> JSON value -> Eiffel value + create parser.make_parser ("true") + if attached {JSON_BOOLEAN} parser.parse as l_jb then + if attached {BOOLEAN} json.object (l_jb, Void) as l_b then + assert ("l_b = True", l_b = True) + else + assert ("json.object (l_jb, Void) is BOOLEAN", False) + end + else + assert ("parser.parse is a JSON_BOOLEAN", False) + end + + -- Eiffel value -> JSON value -> JSON representation + b := False + create jb.make_boolean (b) + assert ("jb.representation.same_string (%"false%")", jb.representation.same_string ("false")) + -- Eiffel value -> JSON value -> JSON representation with factory + if attached {JSON_BOOLEAN} json.value (b) as l_jb then + assert ("l_jb.representation.same_string (%"false%")", l_jb.representation.same_string ("false")) + else + assert ("json.value (b) is a JSON_BOOLEAN", False) + end + + -- JSON representation -> JSON value -> Eiffel value + create parser.make_parser ("false") + if attached {JSON_BOOLEAN} parser.parse as l_jb then + if attached {BOOLEAN} json.object (l_jb, Void) as l_b then + assert ("l_b = False", l_b = False) + else + assert ("json.object (l_jb, Void) is a BOOLEAN", False) + end + else + assert ("parser.parse is a JSON_BOOLEAN", False) + end + end + + test_json_null + local + a: detachable ANY + dummy_object: STRING + jn: detachable JSON_NULL + jrep: STRING + parser: JSON_PARSER + do + -- Eiffel value -> JSON value -> JSON representation + create jn + assert ("jn /= Void", jn /= Void) + assert ("jn.representation.is_equal (%"%"null%"%")", jn.representation.is_equal ("null")) + -- Eiffel value -> JSON value -> JSON representation with factory + jn ?= json.value (Void) + assert ("jn /= Void", jn /= Void) + if attached jn as l_jn then + assert ("jn.representation.is_equal (%"null%")", l_jn.representation.is_equal ("null")) + end + + -- JSON representation -> JSON value -> Eiffel value + jrep := "null" + create parser.make_parser (jrep) + jn := Void + jn ?= parser.parse + assert ("jn /= Void", jn /= Void) + create dummy_object.make_empty + a := dummy_object + a ?= json.object (jn, Void) + assert ("a = Void", a = Void) + end + + test_json_string_and_character + local + c: CHARACTER + js: detachable JSON_STRING + jrep: STRING + parser: JSON_PARSER + do + c := 'a' + -- Eiffel value -> JSON value -> JSON representation + create js.make_json (c.out) + assert ("js /= Void", js /= Void) + assert ("js.representation.is_equal (%"%"a%"%")", js.representation.is_equal ("%"a%"")) + -- Eiffel value -> JSON value -> JSON representation with factory + js ?= json.value (c) + assert ("js /= Void", js /= Void) + if attached js as l_js then + assert ("js.representation.is_equal (%"%"a%"%")", l_js.representation.is_equal ("%"a%"")) + end + + -- JSON representation -> JSON value -> Eiffel value + jrep := "%"a%"" + create parser.make_parser (jrep) + js := Void + js ?= parser.parse + assert ("js /= Void", js /= Void) + if attached {UC_STRING} json.object (js, "UC_STRING") as ucs then + assert ("ucs.string.is_equal (%"a%")", ucs.string.is_equal ("a")) + end + end + + test_json_string_and_string + local + s: STRING + js: detachable JSON_STRING + jrep: STRING + parser: JSON_PARSER + do + s := "foobar" + -- Eiffel value -> JSON value -> JSON representation + create js.make_json (s) + assert ("js /= Void", js /= Void) + assert ("js.representation.is_equal (%"%"foobar%"%")", js.representation.is_equal ("%"foobar%"")) + -- Eiffel value -> JSON value -> JSON representation with factory + js ?= json.value (s) + assert ("js /= Void", js /= Void) + if attached js as l_js then + assert ("js.representation.is_equal (%"%"foobar%"%")", js.representation.is_equal ("%"foobar%"")) + end + + -- JSON representation -> JSON value -> Eiffel value + jrep := "%"foobar%"" + create parser.make_parser (jrep) + js := Void + js ?= parser.parse + assert ("js /= Void", js /= Void) + if attached {UC_STRING} json.object (js, "UC_STRING") as l_ucs then + assert ("ucs.string.is_equal (%"foobar%")", l_ucs.string.is_equal ("foobar")) + end + end + + test_json_string_and_uc_string + local + js: detachable JSON_STRING + ucs: detachable UC_STRING + jrep: STRING + parser: JSON_PARSER + do + create ucs.make_from_string ("foobar") + -- Eiffel value -> JSON value -> JSON representation + create js.make_json (ucs) + assert ("js /= Void", js /= Void) + assert ("js.representation.is_equal (%"%"foobar%"%")", js.representation.is_equal ("%"foobar%"")) + -- Eiffel value -> JSON value -> JSON representation with factory + js ?= json.value (ucs) + assert ("js /= Void", js /= Void) + if attached js as l_js then + assert ("js.representation.is_equal (%"%"foobar%"%")", l_js.representation.is_equal ("%"foobar%"")) + end + + -- JSON representation -> JSON value -> Eiffel value + jrep := "%"foobar%"" + create parser.make_parser (jrep) + js := Void + js ?= parser.parse + assert ("js /= Void", js /= Void) + ucs := Void + ucs ?= json.object (js, "UC_STRING") + if attached ucs as l_ucs then + assert ("ucs.string.is_equal (%"foobar%")", l_ucs.string.is_equal ("foobar")) + end + end + + test_json_array + local + ll: LINKED_LIST [INTEGER_8] + ll2: detachable LINKED_LIST [detachable ANY] + ja: detachable JSON_ARRAY + jn: JSON_NUMBER + jrep: STRING + parser: JSON_PARSER + do + -- Eiffel value -> JSON value -> JSON representation + create ll.make + ll.extend (0) + ll.extend (1) + ll.extend (1) + ll.extend (2) + ll.extend (3) + ll.extend (5) + -- Note: Currently there is no simple way of creating a JSON_ARRAY + -- from an LINKED_LIST. + create ja.make_array + from + ll.start + until + ll.after + loop + create jn.make_integer (ll.item) + ja.add (jn) + ll.forth + end + assert ("ja /= Void", ja /= Void) + assert ("ja.representation.is_equal (%"[0,1,1,2,3,5]%")", ja.representation.is_equal ("[0,1,1,2,3,5]")) + -- Eiffel value -> JSON value -> JSON representation with factory + ja := Void + ja ?= json.value (ll) + assert ("ja /= Void", ja /= Void) + if attached ja as l_ja then + assert ("ja.representation.is_equal (%"[0,1,1,2,3,5]%")", l_ja.representation.is_equal ("[0,1,1,2,3,5]")) + end + + -- JSON representation -> JSON value -> Eiffel value + -- Note: The JSON_FACTORY will return the smallest INTEGER_* object + -- that can represent the value of the JSON number, in this case + -- it means we will get an LINKED_LIST [ANY] containing the INTEGER_8 + -- values 0, 1, 1, 2, 3, 5 + jrep := "[0,1,1,2,3,5]" + create parser.make_parser (jrep) + ja := Void + ja ?= parser.parse + assert ("ja /= Void", ja /= Void) + ll2 ?= json.object (ja, Void) + assert ("ll2 /= Void", ll2 /= Void) + --ll.compare_objects + --ll2.compare_objects + if attached ll2 as l_ll2 then + assert ("ll2.is_equal (ll)", l_ll2.is_equal (ll)) + end + + end + + test_json_object + local + t, t2: detachable DS_HASH_TABLE [detachable ANY, HASHABLE] + i: INTEGER + ucs_key, ucs: UC_STRING + a: ARRAY [INTEGER] + jo: detachable JSON_OBJECT + jn: JSON_NUMBER + js_key, js: JSON_STRING + ja: JSON_ARRAY + jrep: STRING + parser: JSON_PARSER + do + -- Eiffel value -> JSON value -> JSON representation + -- Note: Currently there is now way of creating a JSON_OBJECT from + -- a DS_HASH_TABLE, so we do it manually. + -- t = {"name": "foobar", "size": 42, "contents", [0, 1, 1, 2, 3, 5]} + json.add_converter (create {JSON_DS_HASH_TABLE_CONVERTER}.make) + json.add_converter (create {JSON_UC_STRING_CONVERTER}.make) + create jo.make + create js_key.make_json ("name") + create js.make_json ("foobar") + jo.put (js, js_key) + create js_key.make_json ("size") + create jn.make_integer (42) + jo.put (jn, js_key) + create js_key.make_json ("contents") + create ja.make_array + create jn.make_integer (0) + ja.add (jn) + create jn.make_integer (1) + ja.add (jn) + create jn.make_integer (1) + ja.add (jn) + create jn.make_integer (2) + ja.add (jn) + create jn.make_integer (3) + ja.add (jn) + create jn.make_integer (5) + ja.add (jn) + jo.put (ja, js_key) + assert ("jo /= Void", jo /= Void) + assert ("jo.representation.is_equal (%"{%"name%":%"foobar%",%"size%":42,%"contents%":[0,1,1,2,3,5]}%")", jo.representation.is_equal ("{%"name%":%"foobar%",%"size%":42,%"contents%":[0,1,1,2,3,5]}")) + -- Eiffel value -> JSON value -> JSON representation with factory + create t.make (3) + create ucs_key.make_from_string ("name") + create ucs.make_from_string ("foobar") + t.put (ucs, ucs_key) + create ucs_key.make_from_string ("size") + i := 42 + t.put (i, ucs_key) + create ucs_key.make_from_string ("contents") + a := <<0, 1, 1, 2, 3, 5>> + t.put (a, ucs_key) + jo := Void + jo ?= json.value (t) + assert ("jo /= Void", jo /= Void) + if attached jo as l_jo then + assert ("jo.representation.is_equal (%"{%"name%":%"foobar%",%"size%":42,%"contents%":[0,1,1,2,3,5]}%")", l_jo.representation.is_equal ("{%"name%":%"foobar%",%"size%":42,%"contents%":[0,1,1,2,3,5]}")) + end + -- JSON representation -> JSON value -> Eiffel value -> JSON value -> JSON representation + jrep := "{%"name%":%"foobar%",%"size%":42,%"contents%":[0,1,1,2,3,5]}" + create parser.make_parser (jrep) + jo := Void + jo ?= parser.parse + assert ("jo /= Void", jo /= Void) + t2 ?= json.object (jo, "DS_HASH_TABLE") + assert ("t2 /= Void", t2 /= Void) + jo ?= json.value (t2) + assert ("jo /= Void", jo /= Void) + if attached jo as l_jo then + assert ("jrep.is_equal (jo.representation)", jrep.is_equal (jo.representation)) + end + end + + test_json_failed_json_conversion + -- Test converting an Eiffel object to JSON that is based on a class + -- for which no JSON converter has been registered. + local + gv: KL_GOBO_VERSION + jv: JSON_VALUE + exception: BOOLEAN + do + if not exception then + create gv + jv := json.value (gv) + else + -- exceptions.is_developer_exception is not implemented in gec + if Eiffel_compiler.is_ise then + assert ("exceptions.is_developer_exception", exceptions.is_developer_exception) + -- assert ("exceptions.is_developer_exception_of_name", exceptions.is_developer_exception_of_name ("eJSON exception: Failed to convert Eiffel object to a JSON_VALUE: KL_GOBO_VERSION")) + end + end + rescue + exception := True + retry + end + + test_json_failed_eiffel_conversion + -- Test converting from a JSON value to an Eiffel object based on a + -- class for which no JSON converter has been registered. + local + gv: KL_GOBO_VERSION + jo: JSON_OBJECT + exception: BOOLEAN + do + if not exception then + create jo.make + gv ?= json.object (jo, "KL_GOBO_VERSION") + else + -- exceptions.is_developer_exception is not implemented in gec + if Eiffel_compiler.is_ise then + assert ("exceptions.is_developer_exception", exceptions.is_developer_exception) + -- assert ("exceptions.is_developer_exception_of_name", exceptions.is_developer_exception_of_name ("eJSON exception: Failed to convert JSON_VALUE to an Eiffel object: JSON_OBJECT -> KL_GOBO_VERSION")) + end + end + rescue + exception := True + retry + end + +end -- class TEST_JSON_CORE diff --git a/test/getest/test_json_custom_classes.e b/test/getest/test_json_custom_classes.e new file mode 100644 index 0000000..59d15c3 --- /dev/null +++ b/test/getest/test_json_custom_classes.e @@ -0,0 +1,50 @@ +class TEST_JSON_CUSTOM_CLASSES + +inherit + SHARED_EJSON + + TS_TEST_CASE + +create + make_default + +feature {NONE} -- Initialization + + make + -- Create test object. + do + end + +feature -- Test + + test_custom_classes + local + bc: BOOK_COLLECTION + jbc: JSON_BOOK_CONVERTER + jbcc: JSON_BOOK_COLLECTION_CONVERTER + jac: JSON_AUTHOR_CONVERTER + jo: JSON_OBJECT + parser: JSON_PARSER + jrep: STRING + do + Json.add_converter (create {JSON_UC_STRING_CONVERTER}.make) + create jbc.make + json.add_converter (jbc) + create jbcc.make + json.add_converter (jbcc) + create jac.make + json.add_converter (jac) + jrep := "{%"name%":%"Test collection%",%"books%":[{%"title%":%"eJSON: The Definitive Guide%",%"isbn%":%"123123-413243%",%"author%":{%"name%":%"Foo Bar%"}}]}" + create parser.make_parser (jrep) + jo := Void + jo ?= parser.parse + assert ("jo /= Void", jo /= Void) + bc := Void + bc ?= json.object (jo, "BOOK_COLLECTION") + assert ("bc /= Void", bc /= Void) + jo ?= json.value (bc) + assert ("jo /= Void", jo /= Void) + assert ("JSON representation is correct", jo.representation.is_equal ("{%"name%":%"Test collection%",%"books%":[{%"title%":%"eJSON: The Definitive Guide%",%"isbn%":%"123123-413243%",%"author%":{%"name%":%"Foo Bar%"}}]}")) + end + +end -- class TEST_JSON_CUSTOM_CLASS