Merge: json: Refactor the APIs
authorJean Privat <jean@pryen.org>
Mon, 17 Nov 2014 22:55:57 +0000 (17:55 -0500)
committerJean Privat <jean@pryen.org>
Mon, 17 Nov 2014 22:55:57 +0000 (17:55 -0500)
Refactor the `json` library taking feedback of @privat, @Morriar and @xymus into account. Also, correct some bugs related to JSON encoding/decoding and remove ad hoc escaping routines.

TODO: Support Unicode escaping.

Signed-off-by: Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>

Pull-Request: #907
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

21 files changed:
contrib/github_merge.nit
contrib/github_search_for_jni/src/github_search_for_jni.nit
contrib/nitcc/examples/json.sablecc
lib/github_api.nit
lib/json/dynamic.nit
lib/json/error.nit [new file with mode: 0644]
lib/json/json.nit
lib/json/json_lexer.nit
lib/json/static.nit
lib/json_serialization.nit
lib/neo4j/curl_json.nit
lib/neo4j/error.nit [new file with mode: 0644]
lib/neo4j/json_store.nit
lib/neo4j/jsonable.nit [deleted file]
lib/neo4j/neo4j.nit
src/doc/doc_pages.nit
tests/sav/nitserial_args1.res
tests/sav/test_json_static.res
tests/test_json_static.nit
tests/test_neo4j.nit
tests/test_neo4j_batch.nit

index c30732f..0d5e052 100644 (file)
@@ -20,14 +20,14 @@ import template
 
 redef class Object
        # Factorize cast
-       fun json_as_a: Array[nullable Object] do return self.as(Array[nullable Object])
+       fun json_as_a: JsonArray do return self.as(JsonArray)
        # Factorize cast
-       fun json_as_map: Map[String, nullable Object] do return self.as(Map[String, nullable Object])
+       fun json_as_map: JsonObject do return self.as(JsonObject)
 end
 
 redef class GithubCurl
        # Get a given pull request (PR)
-       fun getpr(number: Int): Map[String, nullable Object]
+       fun getpr(number: Int): JsonObject
        do
                var pr = get_and_check("https://api.github.com/repos/privat/nit/pulls/{number}")
                var prm = pr.json_as_map
@@ -46,7 +46,7 @@ redef class GithubCurl
        end
 
        # Get reviewers of a PR
-       fun getrev(pr: Map[String, nullable Object]): Array[String]
+       fun getrev(pr: JsonObject): Array[String]
        do
                var number = pr["number"].as(Int)
                var user = pr["user"].json_as_map["login"].as(String)
index d12f288..35b19b2 100644 (file)
@@ -19,13 +19,12 @@ module github_search_for_jni
 
 import github_api
 
-# The proprieties introduced by this redef are to be used only on HashMap
+# The proprieties introduced by this redef are to be used only on a JSON object
 # representing a Github repository.
-redef class HashMap[K, V]
+redef class JsonObject
        # The repository has at least 50% Java code
        fun has_lots_of_java: Bool
        do
-               assert self isa HashMap[String, nullable Object]
                var java_count = 0
                if keys.has("Java") then java_count = self["Java"].as(Int)
 
@@ -43,7 +42,6 @@ redef class HashMap[K, V]
        # The repository has at least 100 lines of C code
        fun has_some_c: Bool
        do
-               assert self isa HashMap[String, nullable Object]
                var c_count = 0
                if keys.has("C") then c_count = self["C"].as(Int)
                return c_count > 100
@@ -64,14 +62,14 @@ var per_page = 100
 loop
        # Get a page of the main query
        var uri = "https://api.github.com/search/repositories?q={main_query}&page={page}&per_page={per_page}&sort=stars"
-       var obj = curl.get_and_check(uri).as(HashMap[String, nullable Object])
+       var obj = curl.get_and_check(uri).as(JsonObject)
 
        # Main object has "total_count" and "items"
-       var items = obj["items"].as(Array[nullable Object])
+       var items = obj["items"].as(JsonArray)
 
        # "items" is an array of Json objects
        for item in items do
-               assert item isa HashMap[String, nullable Object]
+               assert item isa JsonObject
 
                # Each item has "name" and "languages_url"
                assert item.keys.has("name")
@@ -79,7 +77,7 @@ loop
 
                # Download the language list
                var lang_url = item["languages_url"].as(String)
-               var langs = curl.get_and_check(lang_url).as(HashMap[String, nullable Object])
+               var langs = curl.get_and_check(lang_url).as(JsonObject)
 
                # The project is of interest if it has lots of Java and at least some C
                var may_be_of_interest = langs.has_lots_of_java and langs.has_some_c
index d1e9c6b..0de8914 100644 (file)
@@ -9,7 +9,18 @@ frac = '.' d+;
 exp = e d+;
 e = ('e'|'E') ('+'|'-')?;
 
-string = '"' (Any-'\\'-'"' | '\\'Any)* '"';
+hexdig = '0'..'9' | 'a'..'z' | 'A'..'Z';
+string = '"' (Any - '\\' - '"' | '\\' (
+               '\\' |
+               '"' |
+               '/' |
+               'b' |
+               'f' |
+               'n' |
+               'r' |
+               't' |
+               'u' hexdig hexdig hexdig hexdig
+       ))* '"';
 
 blank = (' '|'\n'|'\t')+;
 
index 2394592..39fca84 100644 (file)
@@ -45,7 +45,7 @@ class GithubCurl
 
        # Get the requested URI, and check the HTTP response. Then convert to JSON
        # and check for Github errors.
-       fun get_and_check(uri: String): nullable Object
+       fun get_and_check(uri: String): nullable Jsonable
        do
                var request = new CurlHTTPRequest(uri, self)
                request.user_agent = user_agent
@@ -53,8 +53,8 @@ class GithubCurl
                var response = request.execute
 
                if response isa CurlResponseSuccess then
-                       var obj = response.body_str.json_to_nit_object
-                       if obj isa HashMap[String, nullable Object] then
+                       var obj = response.body_str.parse_json
+                       if obj isa JsonObject then
                                if obj.keys.has("message") then
                                        print "Message from Github API: {obj["message"] or else ""}"
                                        print "Requested URI: {uri}"
index ec3ba25..d15ceb7 100644 (file)
@@ -20,8 +20,8 @@
 # to get the underlying Json data. It can also be used as any Json types.
 module dynamic
 
+import error
 private import static
-import standard
 
 class JsonValue
        var value: nullable Object
@@ -123,14 +123,14 @@ class JsonValue
        #
        #     assert """{"a": 123}""".to_json_value.is_map
        #     assert not "123".to_json_value.is_map
-       fun is_map: Bool do return value isa HashMap[String, nullable Object]
+       fun is_map: Bool do return value isa MapRead[String, nullable Object]
 
        # Get this value as a `Map[String, JsonValue]`
        #
        # require: `self.is_map`
        fun to_map: Map[String, JsonValue] do
                var value = value
-               assert value isa HashMap[String, nullable Object]
+               assert value isa MapRead[String, nullable Object]
 
                var map = new HashMap[String, JsonValue]
                for k, v in value do map[k] = new JsonValue(v)
@@ -145,7 +145,7 @@ class JsonValue
        #     assert "[1, 2, 3, 4, 5]".to_json_value.is_array
        #     assert "[null, true, false, 0.0, 1, \"str\"]".to_json_value.is_array
        #     assert """["a", "b", "c"]""".to_json_value.is_array
-       fun is_array: Bool do return value isa Array[nullable Object]
+       fun is_array: Bool do return value isa SequenceRead[nullable Object]
 
        # Get this value as an `Array[JsonValue]`
        #
@@ -155,13 +155,42 @@ class JsonValue
        fun to_a: Array[JsonValue]
        do
                var value = value
-               assert value isa Array[nullable Object]
+               assert value isa SequenceRead[nullable Object]
 
                var a = new Array[JsonValue]
                for e in value do a.add(new JsonValue(e))
                return a
        end
 
+       ### Error
+
+       # Is this value an error?
+       #
+       #     assert "[]".to_json_value[0].is_error
+       #     assert "[".to_json_value.is_error
+       #     assert not "[]".to_json_value.is_error
+       fun is_error: Bool do return value isa Error
+
+       # Get this value as a `Error`.
+       #
+       # require: `self.is_error`
+       fun to_error: Error do return value.as(Error)
+
+       ### JsonParseError
+
+       # Is this value a parse error?
+       #
+       #     assert "[".to_json_value.is_parse_error
+       #     assert not "[]".to_json_value.is_parse_error
+       fun is_parse_error: Bool do return value isa JsonParseError
+
+       # Get this value as a `JsonParseError`.
+       #
+       # require: `self.is_parse_error`
+       fun to_parse_error: JsonParseError do return value.as(JsonParseError)
+
+       ### Children access
+
        # Iterator over the values of the array `self`
        #
        # require: `self.is_array`
@@ -181,17 +210,39 @@ class JsonValue
        #     assert """{"a": 123}""".to_json_value["a"].to_i == 123
        #     assert """{"123": "a"}""".to_json_value[123].to_s == "a"
        #     assert """{"John Smith": 1980}""".to_json_value[["John ", "Smith"]].to_i == 1980
+       #     assert """{"a": 123}""".to_json_value["b"].is_error
        #
        #     assert """["a", "b", "c"]""".to_json_value[0].to_s == "a"
-       fun [](key: Object): JsonValue
-       do
+       #     assert """["a", "b", "c"]""".to_json_value[3].is_error
+       fun [](key: Object): JsonValue do
                var value = value
-               if value isa HashMap[String, nullable Object] then
-                       return new JsonValue(value[key.to_s])
-               else if value isa Array[nullable Object] then
-                       assert key isa Int
-                       return new JsonValue(value[key])
-               else abort
+               var result: nullable Object
+               if is_error then
+                       return self
+               else if value isa MapRead[String, nullable Object] then
+                       key = key.to_s
+                       if value.has_key(key) then
+                               result = value[key]
+                       else
+                               result = new JsonKeyError("Key `{key}` not found.", self, key)
+                       end
+               else if value isa SequenceRead[nullable Object] then
+                       if key isa Int then
+                               if key < value.length and key >= 0 then
+                                       result = value[key]
+                               else
+                                       result = new JsonKeyError("Index `{key}` out of bounds.",
+                                                       self, key)
+                               end
+                       else
+                               result = new JsonKeyError("Invalid key type. Expecting `Int`. Got `{key.class_name}`.",
+                                               self, key)
+                       end
+               else
+                       result = new JsonKeyError("Invalid `[]` access on a `{json_type}` JsonValue.",
+                                       self, key)
+               end
+               return new JsonValue(result)
        end
 
        # Advanced query to get a value within the map `self` or it's children.
@@ -203,24 +254,78 @@ class JsonValue
        #     assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a").is_map
        #     assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t").to_bool
        #     assert not """{"a": {"t": true, "f": false}}""".to_json_value.get("a.f").to_bool
+       #     assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t.t").is_error
        #     assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.b.c.d").to_i == 123
-       fun get(query: String): JsonValue
-       do
+       #     assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.z.c.d").is_error
+       fun get(query: String): JsonValue do
                var keys = query.split(".")
                var value = value
-               for key in keys do
-                       assert value isa HashMap[String, nullable Object]
-                       value = value[key]
+               if is_error then return self
+               for i in [0..keys.length[ do
+                       var key = keys[i]
+                       if value isa MapRead[String, nullable Object] then
+                               if value.has_key(key) then
+                                       value = value[key]
+                               else
+                                       var sub_query = sub_query_to_s(keys, i)
+                                       var e = new JsonKeyError("Key `{key}` not found.",
+                                                       self, sub_query)
+                                       return new JsonValue(e)
+                               end
+                       else
+                               var sub_query = sub_query_to_s(keys, i)
+                               var val_type = (new JsonValue(value)).json_type
+                               var e = new JsonKeyError("Value at `{sub_query}` is not a map. Got type `{val_type}`",
+                                               self, sub_query)
+                               return new JsonValue(e)
+                       end
                end
                return new JsonValue(value)
        end
+
+       # Concatenate all keys up to `last` for debugging purposes.
+       #
+       # Note: This method deletes elements in `keys`.
+       private fun sub_query_to_s(keys: Array[String], last: Int): String do
+               last += 1
+               for j in [last..keys.length[ do keys.pop
+               return keys.join(".")
+       end
+
+       # Return a human-readable description of the type.
+       #
+       # For debugging purpose only.
+       fun json_type: String do
+               if is_array then return "array"
+               if is_bool then return "bool"
+               if is_float then return "float"
+               if is_int then return "int"
+               if is_null then return "null"
+               if is_map then return "map"
+               if is_string then return "string"
+               if is_parse_error then return "parse_error"
+               if is_error then return "error"
+               return "undefined"
+       end
 end
 
-redef class String
+# Keyed access failed.
+class JsonKeyError
+       super Error
+
+       # The value on which the access was requested.
+       var json_value: JsonValue
+
+       # The requested key.
+       #
+       # In the case of `JsonValue.get`, the sub-query that failed.
+       var key: Object
+end
+
+redef class Text
        # Parse `self` to obtain a `JsonValue`
-       fun to_json_value: JsonValue
-       do
-               var value = json_to_nit_object
+       fun to_json_value: JsonValue do
+               var value = parse_json
                return new JsonValue(value)
        end
 end
diff --git a/lib/json/error.nit b/lib/json/error.nit
new file mode 100644 (file)
index 0000000..dab1689
--- /dev/null
@@ -0,0 +1,31 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Errors related to JSON parsing.
+module json::error
+
+import nitcc_runtime
+
+# Ill-formed JSON.
+class JsonParseError
+       super Error
+
+       # The location of the error in the original text.
+       var position: nullable Position
+
+       redef fun to_s do
+               var p = position
+               if p isa Position then
+                       return "[{p}] {super}"
+               else
+                       return super
+               end
+       end
+end
index 9353350..b458d01 100644 (file)
@@ -1,6 +1,7 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Offers two APIs to manipulate read Json strings.
+# Provides two APIs to manipulate JSON strings.
+#
+# Both `dynamic` and `static` modules provide at least a method to parse a
+# value written in JSON, but only `static` provide a method to translate a
+# value into JSON.
 #
 # The `dynamic` module provides a simple interface to get information
-# from a Json document. You must be careful as all services are provided on
+# from a JSON document. You must be careful as all services are provided on
 # each nodes and a wrongful use can `abort`.
 #
-# The `static` module converts a Json string to a nullable Nit object. The object
-# must then be type checked before it can be used.
+# The `static` module provides a common interface for anything that can be
+# translated into a JSON document. The provided parsing method return a
+# nullable Nit object that must then be type-checked before it can be used.
 module json
 
 import static
index f597c82..3273525 100644 (file)
@@ -36,6 +36,10 @@ redef class Object
        private fun dfastate_28: DFAState28 do return once new DFAState28
        private fun dfastate_29: DFAState29 do return once new DFAState29
        private fun dfastate_30: DFAState30 do return once new DFAState30
+       private fun dfastate_31: DFAState31 do return once new DFAState31
+       private fun dfastate_32: DFAState32 do return once new DFAState32
+       private fun dfastate_33: DFAState33 do return once new DFAState33
+       private fun dfastate_34: DFAState34 do return once new DFAState34
 end
 class MyNToken
        super NToken
@@ -391,6 +395,75 @@ private class DFAState30
        super DFAState
        redef fun trans(char) do
                var c = char.ascii
-               return dfastate_2
+               if c <= 33 then return null
+               if c <= 34 then return dfastate_2
+               if c <= 46 then return null
+               if c <= 47 then return dfastate_2
+               if c <= 91 then return null
+               if c <= 92 then return dfastate_2
+               if c <= 97 then return null
+               if c <= 98 then return dfastate_2
+               if c <= 101 then return null
+               if c <= 102 then return dfastate_2
+               if c <= 109 then return null
+               if c <= 110 then return dfastate_2
+               if c <= 113 then return null
+               if c <= 114 then return dfastate_2
+               if c <= 115 then return null
+               if c <= 116 then return dfastate_2
+               if c <= 117 then return dfastate_31
+               return null
+       end
+end
+private class DFAState31
+       super DFAState
+       redef fun trans(char) do
+               var c = char.ascii
+               if c <= 47 then return null
+               if c <= 57 then return dfastate_32
+               if c <= 64 then return null
+               if c <= 90 then return dfastate_32
+               if c <= 96 then return null
+               if c <= 122 then return dfastate_32
+               return null
+       end
+end
+private class DFAState32
+       super DFAState
+       redef fun trans(char) do
+               var c = char.ascii
+               if c <= 47 then return null
+               if c <= 57 then return dfastate_33
+               if c <= 64 then return null
+               if c <= 90 then return dfastate_33
+               if c <= 96 then return null
+               if c <= 122 then return dfastate_33
+               return null
+       end
+end
+private class DFAState33
+       super DFAState
+       redef fun trans(char) do
+               var c = char.ascii
+               if c <= 47 then return null
+               if c <= 57 then return dfastate_34
+               if c <= 64 then return null
+               if c <= 90 then return dfastate_34
+               if c <= 96 then return null
+               if c <= 122 then return dfastate_34
+               return null
+       end
+end
+private class DFAState34
+       super DFAState
+       redef fun trans(char) do
+               var c = char.ascii
+               if c <= 47 then return null
+               if c <= 57 then return dfastate_2
+               if c <= 64 then return null
+               if c <= 90 then return dfastate_2
+               if c <= 96 then return null
+               if c <= 122 then return dfastate_2
+               return null
        end
 end
index 51800fc..29e5c22 100644 (file)
@@ -1,6 +1,8 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Alexandre Terrasa <alexandre@moz-concept.com>
+# Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 
 # Static interface to get Nit objects from a Json string.
 #
-# `String::json_to_nit_object` returns an equivalent Nit object from
+# `Text::parse_json` returns an equivalent Nit object from
 # the Json source. This object can then be type checked by the usual
 # languages features (`isa` and `as`).
 module static
 
-import standard
+import error
 private import json_parser
 private import json_lexer
 
+# Something that can be translated to JSON.
+interface Jsonable
+       # Encode `self` in JSON.
+       #
+       # SEE: `append_json`
+       fun to_json: String is abstract
+
+       # Append the JSON representation of `self` to the specified buffer.
+       #
+       # SEE: `to_json`
+       fun append_json(buffer: Buffer) do
+               buffer.append(to_json)
+       end
+end
+
+redef class Text
+       super Jsonable
+
+       redef fun append_json(buffer) do
+               buffer.add '\"'
+               for i in [0..self.length[ do
+                       var char = self[i]
+                       if char == '\\' then
+                               buffer.append "\\\\"
+                       else if char == '\"' then
+                               buffer.append "\\\""
+                       else if char == '\/' then
+                               buffer.append "\\/"
+                       else if char < 16.ascii then
+                               if char == '\n' then
+                                       buffer.append "\\n"
+                               else if char == '\r' then
+                                       buffer.append "\\r"
+                               else if char == '\t' then
+                                       buffer.append "\\t"
+                               else if char == 0x0C.ascii then
+                                       buffer.append "\\f"
+                               else if char == 0x08.ascii then
+                                       buffer.append "\\b"
+                               else
+                                       buffer.append "\\u000{char.ascii.to_hex}"
+                               end
+                       else if char < ' ' then
+                               buffer.append "\\u00{char.ascii.to_hex}"
+                       else
+                               buffer.add char
+                       end
+               end
+               buffer.add '\"'
+       end
+
+       # Encode `self` in JSON.
+       #
+       #     assert "\t\"http://example.com\"\r\n\0\\".to_json ==
+       #               "\"\\t\\\"http:\\/\\/example.com\\\"\\r\\n\\u0000\\\\\""
+       redef fun to_json do
+               var buffer = new FlatBuffer
+               append_json(buffer)
+               return buffer.write_to_string
+       end
+
+       # Parse `self` as JSON.
+       #
+       # If `self` is not a valid JSON document or contains an unsupported escape
+       # sequence, return a `JSONParseError`.
+       #
+       # Example with `JsonObject`:
+       #
+       #     var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
+       #     assert obj isa JsonObject
+       #     assert obj["foo"] isa JsonObject
+       #     assert obj["foo"].as(JsonObject)["bar"] == true
+       #
+       # Example with `JsonArray`:
+       #
+       #     var arr = "[1, 2, 3]".parse_json
+       #     assert arr isa JsonArray
+       #     assert arr.length == 3
+       #     assert arr.first == 1
+       #     assert arr.last == 3
+       #
+       # Example with `String`:
+       #
+       #     var str = "\"foo, bar, baz\"".parse_json
+       #     assert str isa String
+       #     assert str == "foo, bar, baz"
+       #
+       # Example of a syntaxic error:
+       #
+       #     var bad = "\{foo: \"bar\"\}".parse_json
+       #     assert bad isa JsonParseError
+       #     assert bad.position.col_start == 2
+       fun parse_json: nullable Jsonable do
+               var lexer = new Lexer_json(to_s)
+               var parser = new Parser_json
+               var tokens = lexer.lex
+               parser.tokens.add_all(tokens)
+               var root_node = parser.parse
+               if root_node isa NStart then
+                       return root_node.n_0.to_nit_object
+               else if root_node isa NError then
+                       return new JsonParseError(root_node.message, root_node.position)
+               else abort
+       end
+end
+
+redef class Buffer
+
+       # Append the JSON representation of `jsonable` to `self`.
+       #
+       # Append `"null"` for `null`.
+       private fun append_json_of(jsonable: nullable Jsonable) do
+               if jsonable isa Jsonable then
+                       append jsonable.to_json
+               else
+                       append "null"
+               end
+       end
+end
+
+redef class Int
+       super Jsonable
+
+       # Encode `self` in JSON.
+       #
+       #     assert 0.to_json == "0"
+       #     assert (-42).to_json == "-42"
+       redef fun to_json do return self.to_s
+end
+
+redef class Float
+       super Jsonable
+
+       # Encode `self` in JSON.
+       #
+       # Note: Because this method use `to_s`, it may lose precision.
+       #
+       # ~~~
+       # # Will not work as expected.
+       # # assert (-0.0).to_json == "-0.0"
+       #
+       # assert (.5).to_json == "0.5"
+       # assert (0.0).to_json == "0.0"
+       # ~~~
+       redef fun to_json do return self.to_s
+end
+
+redef class Bool
+       super Jsonable
+
+       # Encode `self` in JSON.
+       #
+       #     assert true.to_json == "true"
+       #     assert false.to_json == "false"
+       redef fun to_json do return self.to_s
+end
+
+# A map that can be translated into a JSON object.
+interface JsonMapRead[K: String, V: nullable Jsonable]
+       super MapRead[K, V]
+       super Jsonable
+
+       redef fun append_json(buffer) do
+               buffer.append "\{"
+               var it = iterator
+               if it.is_ok then
+                       append_json_entry(it, buffer)
+                       while it.is_ok do
+                               buffer.append ","
+                               append_json_entry(it, buffer)
+                       end
+               end
+               it.finish
+               buffer.append "\}"
+       end
+
+       # Encode `self` in JSON.
+       #
+       #     var obj = new JsonObject
+       #     obj["foo"] = "bar"
+       #     assert obj.to_json == "\{\"foo\":\"bar\"\}"
+       #     obj = new JsonObject
+       #     obj["baz"] = null
+       #     assert obj.to_json == "\{\"baz\":null\}"
+       redef fun to_json do
+               var buffer = new FlatBuffer
+               append_json(buffer)
+               return buffer.write_to_string
+       end
+
+       private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
+                       buffer: Buffer) do
+               buffer.append iterator.key.to_json
+               buffer.append ":"
+               buffer.append_json_of(iterator.item)
+               iterator.next
+       end
+end
+
+# A JSON Object.
+class JsonObject
+       super JsonMapRead[String, nullable Jsonable]
+       super HashMap[String, nullable Jsonable]
+end
+
+# A sequence that can be translated into a JSON array.
+class JsonSequenceRead[E: nullable Jsonable]
+       super Jsonable
+       super SequenceRead[E]
+
+       redef fun append_json(buffer) do
+               buffer.append "["
+               var it = iterator
+               if it.is_ok then
+                       append_json_entry(it, buffer)
+                       while it.is_ok do
+                               buffer.append ","
+                               append_json_entry(it, buffer)
+                       end
+               end
+               it.finish
+               buffer.append "]"
+       end
+
+       # Encode `self` in JSON.
+       #
+       #     var arr = new JsonArray.with_items("foo", null)
+       #     assert arr.to_json == "[\"foo\",null]"
+       #     arr.pop
+       #     assert arr.to_json =="[\"foo\"]"
+       #     arr.pop
+       #     assert arr.to_json =="[]"
+       redef fun to_json do
+               var buffer = new FlatBuffer
+               append_json(buffer)
+               return buffer.write_to_string
+       end
+
+       private fun append_json_entry(iterator: Iterator[nullable Jsonable],
+                       buffer: Buffer) do
+               buffer.append_json_of(iterator.item)
+               iterator.next
+       end
+end
+
+# A JSON array.
+class JsonArray
+       super JsonSequenceRead[nullable Jsonable]
+       super Array[nullable Jsonable]
+end
+
+redef class JsonParseError
+       super Jsonable
+
+       # Get the JSON representation of `self`.
+       #
+       #     var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
+       #     assert err.to_json == "\{\"error\":\"JsonParseError\"," +
+       #               "\"position\":\{" +
+       #                       "\"pos_start\":1,\"pos_end\":2," +
+       #                       "\"line_start\":3,\"line_end\":4," +
+       #                       "\"col_start\":5,\"col_end\":6" +
+       #               "\},\"message\":\"foo\"\}"
+       redef fun to_json do
+               return "\{\"error\":\"JsonParseError\"," +
+                               "\"position\":{position.to_json}," +
+                               "\"message\":{message.to_json}\}"
+       end
+end
+
+redef class Position
+       super Jsonable
+
+       # Get the JSON representation of `self`.
+       #
+       #     var pos = new Position(1, 2, 3, 4, 5, 6)
+       #     assert pos.to_json == "\{" +
+       #                       "\"pos_start\":1,\"pos_end\":2," +
+       #                       "\"line_start\":3,\"line_end\":4," +
+       #                       "\"col_start\":5,\"col_end\":6" +
+       #               "\}"
+       redef fun to_json do
+               return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
+                               "\"line_start\":{line_start},\"line_end\":{line_end}," +
+                               "\"col_start\":{col_start},\"col_end\":{col_end}\}"
+       end
+end
+
+################################################################################
+# Redef parser
+
 redef class Nvalue
-       fun to_nit_object: nullable Object is abstract
+       fun to_nit_object: nullable Jsonable is abstract
 end
 
 redef class Nvalue_number
@@ -55,17 +348,46 @@ redef class Nvalue_null
 end
 
 redef class Nstring
-       # FIXME support \n, etc.
-       fun to_nit_string: String do return text.substring(1, text.length-2).
-               replace("\\\\", "\\").replace("\\\"", "\"").replace("\\b", "\b").
-               replace("\\/", "/").replace("\\n", "\n").replace("\\r", "\r").
-               replace("\\t", "\t")
+       fun to_nit_string: String do
+               var res = new FlatBuffer
+               var i = 1
+               while i < text.length - 1 do
+                       var char = text[i]
+                       if char == '\\' then
+                               i += 1
+                               char = text[i]
+                               if char == 'b' then
+                                       char = 0x08.ascii
+                               else if char == 'f' then
+                                       char = 0x0C.ascii
+                               else if char == 'n' then
+                                       char = '\n'
+                               else if char == 'r' then
+                                       char = '\r'
+                               else if char == 't' then
+                                       char = '\t'
+                               else if char == 'u' then
+                                       var code = text.substring(i + 1, 4).to_hex
+                                       # TODO UTF-16 escaping is not supported yet.
+                                       if code >= 128 then
+                                               char = '?'
+                                       else
+                                               char = code.ascii
+                                       end
+                                       i += 4
+                               end
+                               # `"`, `/` or `\` => Keep `char` as-is.
+                       end
+                       res.add char
+                       i += 1
+               end
+               return res.write_to_string
+       end
 end
 
 redef class Nvalue_object
-       redef fun to_nit_object
-       do
-               var obj = new HashMap[String, nullable Object]
+       redef fun to_nit_object do
+               var obj = new JsonObject
                var members = n_members
                if members != null then
                        var pairs = members.pairs
@@ -94,13 +416,13 @@ end
 
 redef class Npair
        fun name: String do return n_string.to_nit_string
-       fun value: nullable Object do return n_value.to_nit_object
+       fun value: nullable Jsonable do return n_value.to_nit_object
 end
 
 redef class Nvalue_array
        redef fun to_nit_object
        do
-               var arr = new Array[nullable Object]
+               var arr = new JsonArray
                var elements = n_elements
                if elements != null then
                        var items = elements.items
@@ -126,25 +448,3 @@ end
 redef class Nelements_head
        redef fun items do return [n_value]
 end
-
-redef class Text
-       fun json_to_nit_object: nullable Object
-       do
-               var lexer = new Lexer_json(to_s)
-               var parser = new Parser_json
-               var tokens = lexer.lex
-               parser.tokens.add_all(tokens)
-               var root_node = parser.parse
-               if root_node isa NStart then
-                       return root_node.n_0.to_nit_object
-               else if root_node isa NLexerError then
-                       var pos = root_node.position
-                       print "Json lexer error: {root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return null
-               else if root_node isa NParserError then
-                       var pos = root_node.position
-                       print "Json parsing error: {root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return null
-               else abort
-       end
-end
index 6a2d82a..f090b24 100644 (file)
@@ -71,16 +71,16 @@ end
 class JsonDeserializer
        super Deserializer
 
-       var root: nullable Object
-       var path = new Array[HashMap[String, nullable Object]]
+       var root: nullable Jsonable
+       var path = new Array[JsonObject]
        var id_to_object = new HashMap[Int, Object]
 
        var just_opened_id: nullable Int = null
 
        init(text: Text)
        do
-               var root = text.json_to_nit_object
-               if root isa HashMap[String, nullable Object] then path.add(root)
+               var root = text.parse_json
+               if root isa JsonObject then path.add(root)
                self.root = root
        end
 
@@ -107,7 +107,7 @@ class JsonDeserializer
        # Convert from simple Json object to Nit object
        private fun convert_object(object: nullable Object): nullable Object
        do
-               if object isa HashMap[String, nullable Object] then
+               if object isa JsonObject then
                        assert object.keys.has("__kind")
                        var kind = object["__kind"]
 
@@ -197,18 +197,11 @@ redef class Bool
 end
 
 redef class Char
-       redef fun serialize_to_json(v) do v.stream.write "\{\"__kind\": \"char\", \"__val\": \"{to_s.to_json_s}\"\}"
+       redef fun serialize_to_json(v) do v.stream.write "\{\"__kind\": \"char\", \"__val\": {to_s.to_json}\}"
 end
 
 redef class String
-       redef fun serialize_to_json(v) do v.stream.write("\"{to_json_s}\"")
-
-       private fun to_json_s: String do return self.replace("\\", "\\\\").
-               replace("\"", "\\\"").replace("/", "\\/").
-               replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
-               # FIXME add support for unicode char when supported by Nit strings
-               # FIXME add support for \f! # .replace("\f", "\\f")
-               # FIXME add support for \b .replace("\b", "\\b")
+       redef fun serialize_to_json(v) do v.stream.write(to_json)
 end
 
 redef class NativeString
@@ -259,7 +252,7 @@ redef class Array[E]
                        v.notify_of_creation self
 
                        var length = v.deserialize_attribute("__length").as(Int)
-                       var arr = v.path.last["__items"].as(Array[nullable Object])
+                       var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
                        for i in length.times do
                                var obj = v.convert_object(arr[i])
                                self.add obj
index dc5c8ac..e458eb1 100644 (file)
@@ -15,7 +15,7 @@
 # cURL requests compatible with the JSON REST APIs.
 module curl_json
 
-import jsonable
+import json::static
 intrude import curl
 
 # An abstract request that defines most of the standard options for Neo4j REST API
diff --git a/lib/neo4j/error.nit b/lib/neo4j/error.nit
new file mode 100644 (file)
index 0000000..f9df000
--- /dev/null
@@ -0,0 +1,34 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Errors thrown by the `neo4j` library.
+module neo4j::error
+
+import json::static
+
+# An error thrown by the `neo4j` API.
+#
+#     var error = new NeoError("ErrorMessage", "ErrorName")
+#     assert error.to_json == "\{\"error\":\"ErrorName\",\"message\":\"ErrorMessage\"\}"
+class NeoError
+       super Error
+       super Jsonable
+
+       # The name of the error.
+       #
+       # Used to programmatically distinguish this kind of error from others.
+       var name: String
+
+       redef fun to_json do
+               return "\{\"error\":{name.to_json},\"message\":{message.to_json}\}"
+       end
+
+       redef fun to_s do return "[{name}] {super}"
+end
index 49e055c..cf9f441 100644 (file)
@@ -70,7 +70,7 @@ class JsonGraph
        #     end
        #     assert 2 == graph.nodes.length
        init from_json(t: Text) do
-               from_json_object(t.to_jsonable.as(JsonObject))
+               from_json_object(t.parse_json.as(JsonObject))
        end
 
        # Retrieve the graph from the specified JSON object.
@@ -138,7 +138,7 @@ redef class NeoNode
        #     assert ["foo", "Bar"] == node.labels
        #     assert 42 == node["baz"]
        init from_json(t: Text) do
-               from_json_object(t.to_jsonable.as(JsonObject))
+               from_json_object(t.parse_json.as(JsonObject))
        end
 
        # Retrieve the node from the specified JSON value.
diff --git a/lib/neo4j/jsonable.nit b/lib/neo4j/jsonable.nit
deleted file mode 100644 (file)
index c82a240..0000000
+++ /dev/null
@@ -1,430 +0,0 @@
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Introduce base classes and services for JSON handling.
-module jsonable
-
-import standard
-private import json::json_parser
-private import json::json_lexer
-
-# Something that can be translated to JSON
-interface Jsonable
-       # Get the JSON representation of `self`
-       fun to_json: String is abstract
-end
-
-redef class String
-       super Jsonable
-
-       redef fun to_json do
-               var res = new FlatBuffer
-               res.add '\"'
-               for i in [0..self.length[ do
-                       var char = self[i]
-                       if char == '\\' then
-                               res.append("\\\\")
-                               continue
-                       else if char == '\"' then
-                               res.append("\\\"")
-                               continue
-                       else if char == '\/' then
-                               res.append("\\/")
-                               continue
-                       else if char == '\n' then
-                               res.append("\\n")
-                               continue
-                       else if char == '\r' then
-                               res.append("\\r")
-                               continue
-                       else if char == '\t' then
-                               res.append("\\t")
-                               continue
-                       end
-                       res.add char
-               end
-               res.add '\"'
-               return res.write_to_string
-       end
-end
-
-redef class Int
-       super Jsonable
-
-       redef fun to_json do return self.to_s
-end
-
-redef class Float
-       super Jsonable
-
-       redef fun to_json do return self.to_s
-end
-
-redef class Bool
-       super Jsonable
-
-       redef fun to_json do return self.to_s
-end
-
-# A JSON Object representation that behaves like a `Map`
-class JsonObject
-       super Jsonable
-       super Map[String, nullable Jsonable]
-
-       private var map = new HashMap[String, nullable Jsonable]
-
-       # Create an empty `JsonObject`
-       #
-       #     var obj = new JsonObject
-       #     assert obj.is_empty
-       init do end
-
-       # Init the JSON Object from a Nit `Map`
-       #
-       #     var map = new HashMap[String, String]
-       #     map["foo"] = "bar"
-       #     map["goo"] = "baz"
-       #     var obj = new JsonObject.from(map)
-       #     assert obj.length == 2
-       #     assert obj["foo"] == "bar"
-       #     assert obj["goo"] == "baz"
-       init from(items: Map[String, nullable Jsonable]) do
-               for k, v in items do map[k] = v
-       end
-
-       redef fun [](key) do return map[key]
-       redef fun []=(key, value) do map[key] = value
-       redef fun clear do map.clear
-       redef fun has_key(key) do return map.has_key(key)
-       redef fun is_empty do return map.is_empty
-       redef fun iterator do return map.iterator
-       redef fun keys do return map.keys
-       redef fun values do return map.values
-       redef fun length do return map.length
-
-       # Advanced query to get a value within `self` or its children.
-       #
-       # A query is composed of the keys to each object seperated by '.'.
-       #
-       # REQUIRE `self.has_key(query)`
-       #
-       #     var obj1 = new JsonObject
-       #     obj1["baz"] = "foobarbaz"
-       #     var obj2 = new JsonObject
-       #     obj2["bar"] = obj1
-       #     var obj3 = new JsonObject
-       #     obj3["foo"] = obj2
-       #     assert obj3.get("foo.bar.baz") == "foobarbaz"
-       fun get(query: String): nullable Jsonable do
-               var keys = query.split(".").reversed
-               var key = keys.pop
-
-               assert has_key(key)
-               var node = self[key]
-
-               while not keys.is_empty do
-                       key = keys.pop
-                       assert node isa JsonObject and node.has_key(key)
-                       node = node[key]
-               end
-               return node
-       end
-
-       # Create an empty `JsonObject`
-       #
-       #     var obj = new JsonObject
-       #     obj["foo"] = "bar"
-       #     assert obj.to_json == "\{\"foo\": \"bar\"\}"
-       redef fun to_json do
-               var tpl = new Array[String]
-               tpl.add "\{"
-               var vals = new Array[String]
-               for k, v in self do
-                       if v == null then
-                               vals.add "{k.to_json}: null"
-                       else
-                               vals.add "{k.to_json}: {v.to_json}"
-                       end
-               end
-               tpl.add vals.join(",")
-               tpl.add "\}"
-               return tpl.join("")
-       end
-
-       redef fun to_s do return to_json
-end
-
-# A JSON Array representation that behaves like a `Sequence`
-class JsonArray
-       super Jsonable
-       super Sequence[nullable Jsonable]
-
-       private var array = new Array[nullable Jsonable]
-
-       init do end
-
-       # init the JSON Array from a Nit `Collection`
-       init from(items: Collection[nullable Jsonable]) do
-               array.add_all(items)
-       end
-
-       redef fun [](key) do return array[key]
-       redef fun []=(key, value) do array[key] = value
-       redef fun clear do array.clear
-       redef fun insert(item, index) do array.insert(item, index)
-       redef fun is_empty do return array.is_empty
-       redef fun iterator do return array.iterator
-       redef fun length do return array.length
-       redef fun pop do return array.pop
-       redef fun push(value) do array.push(value)
-       redef fun remove_at(index) do array.remove_at(index)
-       redef fun shift do return array.shift
-       redef fun unshift(e) do array.unshift(e)
-
-       redef fun to_json do
-               var tpl = new Array[String]
-               tpl.add "["
-               var vals = new Array[String]
-               for v in self do
-                       if v == null then
-                               vals.add "null"
-                       else
-                               vals.add v.to_json
-                       end
-               end
-               tpl.add vals.join(",")
-               tpl.add "]"
-               return tpl.join("")
-       end
-
-       redef fun to_s do return to_json
-end
-
-# An error in JSON format that can be returned by tools using JSON like parsers.
-#
-#     var error = new JsonError("ErrorCode", "ErrorMessage")
-#     assert error.to_s == "ErrorCode: ErrorMessage"
-#     assert error.to_json == "\{\"error\": \"ErrorCode\", \"message\": \"ErrorMessage\"\}"
-class JsonError
-       super Jsonable
-
-       # The error code
-       var error: String
-
-       # The error message
-       var message: String
-
-       redef fun to_json do
-               var tpl = new Array[String]
-               tpl.add "\{"
-               tpl.add "\"error\": {error.to_json}, "
-               tpl.add "\"message\": {message.to_json}"
-               tpl.add "\}"
-               return tpl.join("")
-       end
-
-       redef fun to_s do return "{error}: {message}"
-end
-
-# Redef parser
-
-redef class Nvalue
-       private fun to_nit_object: nullable Jsonable is abstract
-end
-
-redef class Nvalue_number
-       redef fun to_nit_object
-       do
-               var text = n_number.text
-               if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
-               return text.to_i
-       end
-end
-
-redef class Nvalue_string
-       redef fun to_nit_object do return n_string.to_nit_string
-end
-
-redef class Nvalue_true
-       redef fun to_nit_object do return true
-end
-
-redef class Nvalue_false
-       redef fun to_nit_object do return false
-end
-
-redef class Nvalue_null
-       redef fun to_nit_object do return null
-end
-
-redef class Nstring
-       # FIXME support \n, etc.
-       fun to_nit_string: String do
-               var res = new FlatBuffer
-               var skip = false
-               for i in [1..text.length-2] do
-                       if skip then
-                               skip = false
-                               continue
-                       end
-                       var char = text[i]
-                       if char == '\\' and i < text.length - 2 then
-                               if text[i + 1] == '\\' then
-                                       res.add('\\')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == '\"' then
-                                       res.add('\"')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == '/' then
-                                       res.add('\/')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == 'n' then
-                                       res.add('\n')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == 'r' then
-                                       res.add('\r')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == 't' then
-                                       res.add('\t')
-                                       skip = true
-                                       continue
-                               end
-                       end
-                       res.add char
-               end
-               return res.write_to_string
-       end
-end
-
-redef class Nvalue_object
-       redef fun to_nit_object
-       do
-               var obj = new JsonObject
-               var members = n_members
-               if members != null then
-                       var pairs = members.pairs
-                       for pair in pairs do obj[pair.name] = pair.value
-               end
-               return obj
-       end
-end
-
-redef class Nmembers
-       fun pairs: Array[Npair] is abstract
-end
-
-redef class Nmembers_tail
-       redef fun pairs
-       do
-               var arr = n_members.pairs
-               arr.add n_pair
-               return arr
-       end
-end
-
-redef class Nmembers_head
-       redef fun pairs do return [n_pair]
-end
-
-redef class Npair
-       fun name: String do return n_string.to_nit_string
-       fun value: nullable Jsonable do return n_value.to_nit_object
-end
-
-redef class Nvalue_array
-       redef fun to_nit_object
-       do
-               var arr = new JsonArray
-               var elements = n_elements
-               if elements != null then
-                       var items = elements.items
-                       for item in items do arr.add(item.to_nit_object)
-               end
-               return arr
-       end
-end
-
-redef class Nelements
-       fun items: Array[Nvalue] is abstract
-end
-
-redef class Nelements_tail
-       redef fun items
-       do
-               var items = n_elements.items
-               items.add(n_value)
-               return items
-       end
-end
-
-redef class Nelements_head
-       redef fun items do return [n_value]
-end
-
-redef class Text
-       # Parse a JSON String as Jsonable entities
-       #
-       # Example with `JsonObject`"
-       #
-       #     var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".to_jsonable
-       #     assert obj isa JsonObject
-       #     assert obj["foo"] isa JsonObject
-       #     assert obj["foo"].as(JsonObject)["bar"] == true
-       #
-       # Example with `JsonArray`
-       #
-       #     var arr = "[1, 2, 3]".to_jsonable
-       #     assert arr isa JsonArray
-       #     assert arr.length == 3
-       #     assert arr.first == 1
-       #     assert arr.last == 3
-       #
-       # Example with `String`
-       #
-       #     var str = "\"foo, bar, baz\"".to_jsonable
-       #     assert str isa String
-       #     assert str == "foo, bar, baz"
-       #
-       # Malformed JSON input returns a `JsonError` object
-       #
-       #     var bad = "\{foo: \"bar\"\}".to_jsonable
-       #     assert bad isa JsonError
-       #     assert bad.error == "JsonLexerError"
-       fun to_jsonable: nullable Jsonable
-       do
-               var lexer = new Lexer_json(to_s)
-               var parser = new Parser_json
-               var tokens = lexer.lex
-               parser.tokens.add_all(tokens)
-               var root_node = parser.parse
-               if root_node isa NStart then
-                       return root_node.n_0.to_nit_object
-               else if root_node isa NLexerError then
-                       var pos = root_node.position
-                       var msg =  "{root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return new JsonError("JsonLexerError", msg)
-               else if root_node isa NParserError then
-                       var pos = root_node.position
-                       var msg = "{root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return new JsonError("JsonParsingError", msg)
-               else abort
-       end
-end
-
index b84a60b..efe4f6a 100644 (file)
@@ -64,6 +64,7 @@
 module neo4j
 
 import curl_json
+import error
 
 # Handles Neo4j server start and stop command
 #
@@ -336,29 +337,31 @@ class Neo4jClient
        # Parse the cURL `response` as a JSON string
        private fun parse_response(response: CurlResponse): Jsonable do
                if response isa CurlResponseSuccess then
-                       if response.body_str.is_empty then
+                       var str = response.body_str
+                       if str.is_empty then return new JsonObject
+                       var res = str.parse_json
+                       if res isa JsonParseError then
+                               var e = new NeoError(res.to_s, "JsonParseError")
+                               e.cause = res
+                               return e
+                       end
+                       if res == null then
+                               # empty response wrap it in empty object
                                return new JsonObject
-                       else
-                               var str = response.body_str
-                               var res = str.to_jsonable
-                               if res == null then
-                                       # empty response wrap it in empty object
-                                       return new JsonObject
-                               else if res isa JsonObject and res.has_key("exception") then
-                                       var error = "Neo4jError::{res["exception"] or else "null"}"
-                                       var msg = ""
-                                       if res.has_key("message") then
-                                               msg = res["message"].to_s
-                                       end
-                                       return new JsonError(error, msg.to_json)
-                               else
-                                       return res
+                       else if res isa JsonObject and res.has_key("exception") then
+                               var error = "Neo4jError::{res["exception"] or else "null"}"
+                               var msg = ""
+                               if res.has_key("message") then
+                                       msg = res["message"].to_s
                                end
+                               return new NeoError(msg, error)
+                       else
+                               return res
                        end
                else if response isa CurlResponseFailed then
-                       return new JsonError("Curl error", "{response.error_msg} ({response.error_code})")
+                       return new NeoError("{response.error_msg} ({response.error_code})", "CurlError")
                else
-                       return new JsonError("Curl error", "Unexpected response '{response}'")
+                       return new NeoError("Unexpected response \"{response}\".", "CurlError")
                end
        end
 end
@@ -896,7 +899,7 @@ class NeoBatch
        fun save_edges(edges: Collection[NeoEdge]) do for edge in edges do save_edge(edge)
 
        # Execute the batch and update local nodes
-       fun execute: List[JsonError] do
+       fun execute: List[NeoError] do
                var request = new JsonPOST(client.batch_url, client.curl)
                # request.headers["X-Stream"] = "true"
                var json_jobs = new JsonArray
@@ -908,16 +911,16 @@ class NeoBatch
        end
 
        # Associate data from response in original nodes and edges
-       private fun finalize_batch(response: Jsonable): List[JsonError] do
-               var errors = new List[JsonError]
+       private fun finalize_batch(response: Jsonable): List[NeoError] do
+               var errors = new List[NeoError]
                if not response isa JsonArray then
-                       errors.add(new JsonError("Neo4jError", "Unexpected batch response format"))
+                       errors.add(new NeoError("Unexpected batch response format.", "Neo4jError"))
                        return errors
                end
                # print " {res.length} jobs executed"
                for res in response do
                        if not res isa JsonObject then
-                               errors.add(new JsonError("Neo4jError", "Unexpected job format in batch response"))
+                               errors.add(new NeoError("Unexpected job format in batch response.", "Neo4jError"))
                                continue
                        end
                        var id = res["id"].as(Int)
index 5d71b02..9c04688 100644 (file)
@@ -17,6 +17,7 @@ module doc_pages
 
 import toolcontext
 import doc_model
+private import json::static
 
 redef class ToolContext
        private var opt_dir = new OptionString("output directory", "-d", "--dir")
@@ -212,23 +213,23 @@ class QuickSearch
                var tpl = new Template
                tpl.add "var nitdocQuickSearchRawList=\{ "
                for mmodule in mmodules do
-                       tpl.add "\"{mmodule.name}\":["
-                       tpl.add "\{txt:\"{mmodule.full_name}\",url:\"{mmodule.nitdoc_url}\"\},"
+                       tpl.add "{mmodule.name.to_json}:["
+                       tpl.add "\{txt:{mmodule.full_name.to_json},url:{mmodule.nitdoc_url.to_json}\},"
                        tpl.add "],"
                end
                for mclass in mclasses do
                        var full_name = mclass.intro.mmodule.full_name
-                       tpl.add "\"{mclass.name}\":["
-                       tpl.add "\{txt:\"{full_name}\",url:\"{mclass.nitdoc_url}\"\},"
+                       tpl.add "{mclass.name.to_json}:["
+                       tpl.add "\{txt:{full_name.to_json},url:{mclass.nitdoc_url.to_json}\},"
                        tpl.add "],"
                end
                for mproperty, mprops in mpropdefs do
-                       tpl.add "\"{mproperty}\":["
+                       tpl.add "{mproperty.to_json}:["
                        for mpropdef in mprops do
                                var full_name = mpropdef.mclassdef.mclass.full_name
                                var cls_url = mpropdef.mclassdef.mclass.nitdoc_url
                                var def_url = "{cls_url}#{mpropdef.mproperty.nitdoc_id}"
-                               tpl.add "\{txt:\"{full_name}\",url:\"{def_url}\"\},"
+                               tpl.add "\{txt:{full_name.to_json},url:{def_url.to_json}\},"
                        end
                        tpl.add "],"
                end
index 2ebbe38..e95605c 100644 (file)
@@ -12,8 +12,6 @@ redef class Deserializer
                if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
                if name == "Array[String]" then return new Array[String].from_deserializer(self)
                if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
-               if name == "Array[Match]" then return new Array[Match].from_deserializer(self)
-               if name == "Array[FlatBuffer]" then return new Array[FlatBuffer].from_deserializer(self)
                return super
        end
 end
index 0a122d4..22ab94a 100644 (file)
Binary files a/tests/sav/test_json_static.res and b/tests/sav/test_json_static.res differ
index b510725..db6b1cf 100644 (file)
@@ -1,6 +1,7 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -22,9 +23,11 @@ end
 
 var a = "\{\"__kind\": \"obj\", \"__id\": 0, \"__class\": \"C\", \"a\": \{\"__kind\": \"obj\", \"__id\": 1, \"__class\": \"A\", \"b\": true, \"c\": \"a\", \"f\": 0.123, \"i\": 1234, \"s\": \"asdf\", \"n\": null, \"array\": [88, \"hello\", null]\}, \"b\": \{\"__kind\": \"obj\", \"__id\": 2, \"__class\": \"B\", \"b\": false, \"c\": \"b\", \"f\": 123.123, \"i\": 2345, \"s\": \"hjkl\", \"n\": null, \"array\": [88, \"hello\", null], \"ii\": 1111, \"ss\": \"qwer\"\}, \"aa\": \{\"__kind\": \"ref\", \"__id\": 1\}\}"
 var b = "\{\"__kind\": \"obj\", \"__id\": 0, \"__class\": \"A\", \"b\": true, \"c\": \"a\", \"f\": 0.123, \"i\": 1234, \"s\": \"asdf\", \"n\": null, \"array\": [88, \"hello\", null]\}"
+var c = "\{\"foo\":\"bar\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0020\\u0000\"\}"
+var d = "\{ \"face with tears of joy\" : \"\\uD83D\\uDE02\" \}"
 
-for s in [a, b] do
-       var obj = s.json_to_nit_object
+for s in [a, b, c, d] do
+       var obj = s.parse_json
        print "# Json: {s}"
        print "# Nit: {obj or else "<null>"}"
 end
index f8cbd4b..7ee824c 100644 (file)
@@ -43,7 +43,7 @@ assert res1.is_linked
 print res1["name"].to_s
 print res1["age"].to_s
 print res1["status"].to_s
-print res1["groups"].to_s
+print res1["groups"].to_json
 print res1.labels.join(" ")
 assert res1.out_edges.is_empty
 
@@ -92,7 +92,7 @@ assert res4.is_linked
 print res4["name"].to_s
 print res4["age"].to_s
 print res4["status"].to_s
-print res4["groups"].to_s
+print res4["groups"].to_json
 print res4.labels.join(" ")
 assert res4.in_edges.is_empty
 assert not res4.out_edges.is_empty
index e6ac890..7455557 100644 (file)
@@ -65,7 +65,7 @@ assert res4.is_linked
 print res4["name"].to_s
 print res4["age"].to_s
 print res4["status"].to_s
-print res4["groups"].to_s
+print res4["groups"].to_json
 print res4.labels.join(" ")
 assert res4.in_edges.is_empty
 assert not res4.out_edges.is_empty