# 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
# 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
+ super Serializable
end
redef class Text
super Jsonable
- redef fun append_json(buffer) do
- buffer.add '\"'
- for i in [0..self.length[ do
+ # 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('\\')
+
+ # 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.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}"
+ 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.ascii.to_hex}"
- else
- buffer.add char
+ # `"`, `/` or `\` => Keep `char` as-is.
end
+ res.add char
+ i += 1
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
+ return res.to_s
end
# Parse `self` as JSON.
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]
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 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.
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
+ # The represented value.
private fun to_nit_object: nullable Jsonable is abstract
end
end
redef class Nstring
- 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
+ # The represented string.
+ private fun to_nit_string: String do return text.substring(1, text.length - 2).unescape_json.to_s
end
redef class Nvalue_object
end
redef class Nmembers
+ #Ā All the key-value pairs.
private fun pairs: Array[Npair] is abstract
end
end
redef class Npair
+ # The represented key.
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
end
end
redef class Nelements
+ # All the items.
private fun items: Array[Nvalue] is abstract
end