X-Git-Url: http://nitlanguage.org diff --git a/lib/json/static.nit b/lib/json/static.nit index 4841020..1107973 100644 --- a/lib/json/static.nit +++ b/lib/json/static.nit @@ -18,7 +18,7 @@ # 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 @@ -30,18 +30,66 @@ 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 + + # 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 + # ~~~ + # + # 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 + 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 = """{ + # "foo": 1, + # "bar": true, + # "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 - # 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 + redef fun append_json(buffer) do buffer.add '\"' for i in [0..self.length[ do var char = self[i] @@ -72,10 +120,48 @@ redef class Text end end buffer.add '\"' - return buffer.write_to_string end - fun json_to_nit_object: nullable Jsonable do + # 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 + # 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 @@ -145,6 +231,20 @@ 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 @@ -153,20 +253,29 @@ interface JsonMapRead[K: String, V: nullable Jsonable] # obj = new JsonObject # obj["baz"] = null # assert obj.to_json == "\{\"baz\":null\}" - redef fun to_json do - var buffer = new FlatBuffer - buffer.append "\{" - var it = iterator - if it.is_ok then - append_json_entry(it, buffer) - while it.is_ok do + 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 "," - append_json_entry(it, buffer) end + buffer.append "\n" + i += 1 end - it.finish + indent -= 1 + buffer.append "\t" * indent buffer.append "\}" - return buffer.write_to_string end private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable], @@ -189,16 +298,7 @@ class JsonSequenceRead[E: nullable Jsonable] super Jsonable super SequenceRead[E] - # 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 + redef fun append_json(buffer) do buffer.append "[" var it = iterator if it.is_ok then @@ -210,7 +310,31 @@ class JsonSequenceRead[E: nullable Jsonable] end it.finish buffer.append "]" - return buffer.write_to_string + 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], @@ -231,13 +355,15 @@ redef class JsonParseError # 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}," + @@ -250,12 +376,14 @@ 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}," + @@ -267,7 +395,8 @@ end # Redef parser redef class Nvalue - fun to_nit_object: nullable Jsonable is abstract + # The represented value. + private fun to_nit_object: nullable Jsonable is abstract end redef class Nvalue_number @@ -296,7 +425,8 @@ redef class Nvalue_null end redef class Nstring - fun to_nit_string: String do + # The represented string. + private fun to_nit_string: String do var res = new FlatBuffer var i = 1 while i < text.length - 1 do @@ -346,7 +476,8 @@ redef class Nvalue_object end redef class Nmembers - fun pairs: Array[Npair] is abstract + # All the key-value pairs. + private fun pairs: Array[Npair] is abstract end redef class Nmembers_tail @@ -363,8 +494,11 @@ redef class Nmembers_head 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 + # 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 redef class Nvalue_array @@ -381,7 +515,8 @@ redef class Nvalue_array end redef class Nelements - fun items: Array[Nvalue] is abstract + # All the items. + private fun items: Array[Nvalue] is abstract end redef class Nelements_tail