# See the License for the specific language governing permissions and
# limitations under the License.
-# Static interface to get Nit objects from a Json string.
+# Static interface to read Nit objects from JSON strings
#
-# `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`).
+# `Text::parse_json` returns a simple Nit object from the JSON source.
+# This object can then be type checked as usual with `isa` and `as`.
module static
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
+redef class Text
- # Use `append_json` to implement `to_json`.
- #
- # Therefore, one that redefine `append_json` may use the following
- # redefinition to link `to_json` and `append_json`:
- #
- # ~~~nitish
- # redef fun to_json do return to_json_by_append
- # ~~~
+ # Removes JSON-escaping if necessary in a JSON string
#
- # Note: This is not the default implementation of `to_json` in order to
- # avoid cyclic references between `append_json` and `to_json` when none are
- # implemented.
- protected fun to_json_by_append: String do
- var buffer = new RopeBuffer
- append_json(buffer)
- return buffer.write_to_string
+ # assert "\\\"string\\uD83D\\uDE02\\\"".unescape_json == "\"string😂\""
+ fun unescape_json: Text do
+ if not json_need_escape then return self
+ return self.json_to_nit_string
end
- # Append the JSON representation of `self` to the specified buffer.
+ # Does `self` need treatment from JSON to Nit ?
#
- # SEE: `to_json`
- fun append_json(buffer: Buffer) do buffer.append(to_json)
-
- # Pretty print JSON string.
+ # i.e. is there at least one `\` character in it ?
#
- # ~~~
- # var obj = new JsonObject
- # obj["foo"] = 1
- # obj["bar"] = true
- # var arr = new JsonArray
- # arr.add 2
- # arr.add false
- # arr.add "baz"
- # obj["baz"] = arr
- # var res = obj.to_pretty_json
- # var exp = """{
- # \t"foo": 1,
- # \t"bar": true,
- # \t"baz": [2, false, "baz"]
- # }\n"""
- # assert res == exp
- # ~~~
- fun to_pretty_json: String do
- var res = new FlatBuffer
- pretty_json_visit(res, 0)
- res.add '\n'
- return res.write_to_string
- end
-
- private fun pretty_json_visit(buffer: FlatBuffer, indent: Int) is abstract
-end
-
-redef class Text
- super Jsonable
+ # assert not "string".json_need_escape
+ # assert "\\\"string\\\"".json_need_escape
+ protected fun json_need_escape: Bool do return has('\\')
- redef fun append_json(buffer) do
- buffer.add '\"'
- for i in [0..self.length[ do
+ # Escapes `self` from a JSON string to a Nit string
+ #
+ # assert "\\\"string\\\"".json_to_nit_string == "\"string\""
+ # assert "\\nEscape\\t\\n".json_to_nit_string == "\nEscape\t\n"
+ # assert "\\u0041zu\\uD800\\uDFD3".json_to_nit_string == "Azu𐏓"
+ protected fun json_to_nit_string: String do
+ var res = new FlatBuffer.with_capacity(byte_length)
+ var i = 0
+ var ln = self.length
+ while i < ln 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.code_point 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.code_point then
- buffer.append "\\f"
- else if char == 0x08.code_point then
- buffer.append "\\b"
- else
- buffer.append "\\u000{char.code_point.to_hex}"
+ i += 1
+ char = self[i]
+ if char == 'b' then
+ char = 0x08.code_point
+ else if char == 'f' then
+ char = 0x0C.code_point
+ 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 u16_esc = from_utf16_digit(i + 1)
+ char = u16_esc.code_point
+ if char.is_surrogate and i + 10 < ln then
+ if self[i + 5] == '\\' and self[i + 6] == 'u' then
+ u16_esc <<= 16
+ u16_esc += from_utf16_digit(i + 7)
+ char = u16_esc.to_u32.from_utf16_surr.code_point
+ i += 6
+ else
+ char = 0xFFFD.code_point
+ end
+ end
+ i += 4
end
- else if char < ' ' then
- buffer.append "\\u00{char.code_point.to_hex}"
- else
- buffer.add char
+ # `"`, `/` or `\` => Keep `char` as-is.
end
+ res.add char
+ i += 1
end
- buffer.add '\"'
+ return res.to_s
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 return to_json_by_append
-
# Parse `self` as JSON.
#
# If `self` is not a valid JSON document or contains an unsupported escape
# var bad = "\{foo: \"bar\"\}".parse_json
# assert bad isa JsonParseError
# assert bad.position.col_start == 2
- fun parse_json: nullable Jsonable do
+ fun parse_json: nullable Serializable do
var lexer = new Lexer_json(to_s)
var parser = new Parser_json
var tokens = lexer.lex
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"
+redef class FlatText
+ redef fun json_need_escape do
+ var its = items
+ for i in [first_byte .. last_byte] do
+ if its[i] == 0x5Cu8 then return true
end
+ return false
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]
+interface JsonMapRead[K: String, V: nullable Serializable]
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 return to_json_by_append
-
- redef fun pretty_json_visit(buffer, indent) do
- buffer.append "\{\n"
- indent += 1
- var i = 0
- for k, v in self do
- buffer.append "\t" * indent
- buffer.append "\"{k}\": "
- if v isa JsonObject or v isa JsonArray then
- v.pretty_json_visit(buffer, indent)
- else
- buffer.append v.to_json
- end
- if i < length - 1 then
- buffer.append ","
- end
- buffer.append "\n"
- i += 1
- end
- indent -= 1
- buffer.append "\t" * indent
- buffer.append "\}"
- 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
+ super Serializable
end
# A JSON Object.
class JsonObject
- super JsonMapRead[String, nullable Jsonable]
- super HashMap[String, nullable Jsonable]
+ super JsonMapRead[String, nullable Serializable]
+ super HashMap[String, nullable Serializable]
end
# A sequence that can be translated into a JSON array.
-class JsonSequenceRead[E: nullable Jsonable]
- super Jsonable
+class JsonSequenceRead[E: nullable Serializable]
+ super Serializable
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 return to_json_by_append
-
- redef fun pretty_json_visit(buffer, indent) do
- buffer.append "\["
- var i = 0
- for v in self do
- if v isa JsonObject or v isa JsonArray then
- v.pretty_json_visit(buffer, indent)
- else
- buffer.append v.to_json
- end
- if i < length - 1 then buffer.append ", "
- i += 1
- end
- buffer.append "\]"
- 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
+ super JsonSequenceRead[nullable Serializable]
+ super Array[nullable Serializable]
end
################################################################################
redef class Nvalue
# The represented value.
- private fun to_nit_object: nullable Jsonable is abstract
+ private fun to_nit_object: nullable Serializable is abstract
end
redef class Nvalue_number
redef class Nstring
# The represented string.
- private 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.code_point
- else if char == 'f' then
- char = 0x0C.code_point
- 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.code_point
- end
- i += 4
- end
- # `"`, `/` or `\` => Keep `char` as-is.
- end
- res.add char
- i += 1
- end
- return res.write_to_string
- end
+ private fun to_nit_string: String do return text.substring(1, text.length - 2).unescape_json.to_s
end
redef class Nvalue_object
private fun name: String do return n_string.to_nit_string
# The represented value.
- private fun value: nullable Jsonable do return n_value.to_nit_object
+ private fun value: nullable Serializable do return n_value.to_nit_object
end
redef class Nvalue_array