interface Jsonable
# Encode `self` in JSON.
#
+ # This is a recursive method which can be refined by any subclasses.
+ # To write any `Serializable` object to JSON, see `serialize_to_json`.
+ #
# SEE: `append_json`
fun to_json: String is abstract
# 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
+ var buffer = new FlatBuffer
append_json(buffer)
- return buffer.write_to_string
+ return buffer.to_s
end
# Append the JSON representation of `self` to the specified buffer.
#
# SEE: `to_json`
fun append_json(buffer: Buffer) do buffer.append(to_json)
+
+ # Pretty print JSON string.
+ #
+ # ~~~
+ # 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.to_s
+ end
+
+ private fun pretty_json_visit(buffer: FlatBuffer, indent: Int) is abstract
end
redef class Text
super Jsonable
+ # Removes JSON-escaping if necessary in a JSON 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
+
+ # Does `self` need treatment from JSON to Nit ?
+ #
+ # i.e. is there at least one `\` character in it ?
+ #
+ # 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
+ 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
+ else if char < ' ' 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}"
+ buffer.append char.escape_to_utf16
end
- else if char < ' ' then
- buffer.append "\\u00{char.ascii.to_hex}"
else
buffer.add char
end
buffer.add '\"'
end
+ # 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(bytelen)
+ var i = 0
+ var ln = self.length
+ while i < ln do
+ var char = self[i]
+ if char == '\\' then
+ 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.from_utf16_surr.code_point
+ i += 6
+ else
+ char = 0xFFFD.code_point
+ end
+ end
+ i += 4
+ end
+ # `"`, `/` or `\` => Keep `char` as-is.
+ end
+ res.add char
+ i += 1
+ end
+ 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
+ # ~~~
+ # assert "\t\"http://example.com\"\r\n\0\\".to_json ==
+ # "\"\\t\\\"http://example.com\\\"\\r\\n\\u0000\\\\\""
+ # ~~~
+ redef fun to_json do
+ var b = new FlatBuffer.with_capacity(bytelen)
+ append_json(b)
+ return b.to_s
+ end
# Parse `self` as JSON.
#
end
end
+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 Buffer
# Append the JSON representation of `jsonable` to `self`.
# 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
# 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)
# 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\"\}"
+ # ~~~
+ # 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
+
+ redef fun pretty_json_visit(buf, indents) do
+ buf.clear
+ buf.append(to_json)
+ end
end
redef class Position
# 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" +
- # "\}"
+ # ~~~
+ # 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}," +
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.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
+ private fun to_nit_string: String do return text.substring(1, text.length - 2).unescape_json.to_s
end
redef class Nvalue_object