1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
4 # Copyright 2014 Alexandre Terrasa <alexandre@moz-concept.com>
5 # Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # Static interface to get Nit objects from a Json string.
21 # `Text::parse_json` returns an equivalent Nit object from
22 # the Json source. This object can then be type checked by the usual
23 # languages features (`isa` and `as`).
27 private import json_parser
28 private import json_lexer
30 # Something that can be translated to JSON.
32 # Encode `self` in JSON.
35 fun to_json
: String is abstract
37 # Use `append_json` to implement `to_json`.
39 # Therefore, one that redefine `append_json` may use the following
40 # redefinition to link `to_json` and `append_json`:
43 # redef fun to_json do return to_json_by_append
46 # Note: This is not the default implementation of `to_json` in order to
47 # avoid cyclic references between `append_json` and `to_json` when none are
49 protected fun to_json_by_append
: String do
50 var buffer
= new FlatBuffer
55 # Append the JSON representation of `self` to the specified buffer.
58 fun append_json
(buffer
: Buffer) do buffer
.append
(to_json
)
60 # Pretty print JSON string.
63 # var obj = new JsonObject
66 # var arr = new JsonArray
71 # var res = obj.to_pretty_json
75 # \t"baz": [2, false, "baz"]
79 fun to_pretty_json
: String do
80 var res
= new FlatBuffer
81 pretty_json_visit
(res
, 0)
86 private fun pretty_json_visit
(buffer
: FlatBuffer, indent
: Int) is abstract
92 # Removes JSON-escaping if necessary in a JSON string
94 # assert "\\\"string\\uD83D\\uDE02\\\"".unescape_json == "\"string😂\""
95 fun unescape_json
: Text do
96 if not json_need_escape
then return self
97 return self.json_to_nit_string
100 # Does `self` need treatment from JSON to Nit ?
102 # i.e. is there at least one `\` character in it ?
104 # assert not "string".json_need_escape
105 # assert "\\\"string\\\"".json_need_escape
106 protected fun json_need_escape
: Bool do return has
('\\')
108 redef fun append_json
(buffer
) do
110 for i
in [0 .. self.length
[ do
114 else if char
== '\"' then
116 else if char < ' ' then
119 else if char == '\r' then
121 else if char == '\t' then
124 buffer.append char.escape_to_utf16
133 # Escapes `self` from a JSON string to a Nit string
135 # assert "\\\"string\\\"".json_to_nit_string == "\"string\""
136 # assert "\\nEscape\\t\\n".json_to_nit_string == "\nEscape\t\n"
137 # assert "\\u0041zu\\uD800\\uDFD3".json_to_nit_string == "Azu𐏓"
138 protected fun json_to_nit_string: String do
139 var res = new FlatBuffer.with_capacity(bytelen)
141 while i < self.length do
147 char = 0x08.code_point
148 else if char == 'f
' then
149 char = 0x0C.code_point
150 else if char == 'n
' then
152 else if char == 'r
' then
154 else if char == 't
' then
156 else if char == 'u
' then
157 var code = substring(i + 1, 4)
159 if hx >= 0xD800 and hx <= 0xDFFF then
160 var lostr = substring(i + 7, 4)
161 if lostr.length < 4 then
166 hx = hx.from_utf16_surr
173 # `"`, `/` or `\` => Keep `char` as-is.
182 # Encode `self` in JSON.
185 # assert "\t\"http://example.com\"\r\n\0\\".to_json ==
186 # "\"\\t\\\"http://example.com\\\"\\r\\n\\u0000\\\\\""
189 var b = new FlatBuffer.with_capacity(bytelen)
194 # Parse `self` as JSON.
196 # If `self` is not a valid JSON document or contains an unsupported escape
197 # sequence, return a `JSONParseError`.
199 # Example with `JsonObject`:
201 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
202 # assert obj isa JsonObject
203 # assert obj["foo"] isa JsonObject
204 # assert obj["foo"].as(JsonObject)["bar"] == true
206 # Example with `JsonArray`:
208 # var arr = "[1, 2, 3]".parse_json
209 # assert arr isa JsonArray
210 # assert arr.length == 3
211 # assert arr.first == 1
212 # assert arr.last == 3
214 # Example with `String`:
216 # var str = "\"foo, bar, baz\"".parse_json
217 # assert str isa String
218 # assert str == "foo, bar, baz"
220 # Example of a syntaxic error:
222 # var bad = "\{foo: \"bar\"\}".parse_json
223 # assert bad isa JsonParseError
224 # assert bad.position.col_start == 2
225 fun parse_json: nullable Jsonable do
226 var lexer = new Lexer_json(to_s)
227 var parser = new Parser_json
228 var tokens = lexer.lex
229 parser.tokens.add_all(tokens)
230 var root_node = parser.parse
231 if root_node isa NStart then
232 return root_node.n_0.to_nit_object
233 else if root_node isa NError then
234 return new JsonParseError(root_node.message, root_node.position)
240 redef fun json_need_escape do
242 for i in [first_byte .. last_byte] do
243 if its[i] == 0x5Cu8 then return true
251 # Append the JSON representation of `jsonable` to `self`.
253 # Append `"null"` for `null`.
254 private fun append_json_of(jsonable: nullable Jsonable) do
255 if jsonable isa Jsonable then
256 append jsonable.to_json
266 # Encode `self` in JSON.
268 # assert 0.to_json == "0"
269 # assert (-42).to_json == "-42"
270 redef fun to_json do return self.to_s
276 # Encode `self` in JSON.
278 # Note: Because this method use `to_s`, it may lose precision.
281 # # Will not work as expected.
282 # # assert (-0.0).to_json == "-0.0"
284 # assert (.5).to_json == "0.5"
285 # assert (0.0).to_json == "0.0"
287 redef fun to_json do return self.to_s
293 # Encode `self` in JSON.
295 # assert true.to_json == "true"
296 # assert false.to_json == "false"
297 redef fun to_json do return self.to_s
300 # A map that can be translated into a JSON object.
301 interface JsonMapRead[K: String, V: nullable Jsonable]
305 redef fun append_json(buffer) do
309 append_json_entry(it, buffer)
312 append_json_entry(it, buffer)
319 # Encode `self` in JSON.
321 # var obj = new JsonObject
323 # assert obj.to_json == "\{\"foo\":\"bar\"\}"
324 # obj = new JsonObject
326 # assert obj.to_json == "\{\"baz\":null\}"
327 redef fun to_json do return to_json_by_append
329 redef fun pretty_json_visit(buffer, indent) do
334 buffer.append "\t" * indent
335 buffer.append "\"{k}\": "
336 if v isa JsonObject or v isa JsonArray then
337 v.pretty_json_visit(buffer, indent)
339 buffer.append v.to_json
341 if i < length - 1 then
348 buffer.append "\t" * indent
352 private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
354 buffer.append iterator.key.to_json
356 buffer.append_json_of(iterator.item)
363 super JsonMapRead[String, nullable Jsonable]
364 super HashMap[String, nullable Jsonable]
367 # A sequence that can be translated into a JSON array.
368 class JsonSequenceRead[E: nullable Jsonable]
370 super SequenceRead[E]
372 redef fun append_json(buffer) do
376 append_json_entry(it, buffer)
379 append_json_entry(it, buffer)
386 # Encode `self` in JSON.
388 # var arr = new JsonArray.with_items("foo", null)
389 # assert arr.to_json == "[\"foo\",null]"
391 # assert arr.to_json =="[\"foo\"]"
393 # assert arr.to_json =="[]"
394 redef fun to_json do return to_json_by_append
396 redef fun pretty_json_visit(buffer, indent) do
400 if v isa JsonObject or v isa JsonArray then
401 v.pretty_json_visit(buffer, indent)
403 buffer.append v.to_json
405 if i < length - 1 then buffer.append ", "
411 private fun append_json_entry(iterator: Iterator[nullable Jsonable],
413 buffer.append_json_of(iterator.item)
420 super JsonSequenceRead[nullable Jsonable]
421 super Array[nullable Jsonable]
424 redef class JsonParseError
427 # Get the JSON representation of `self`.
430 # var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
431 # assert err.to_json == "\{\"error\":\"JsonParseError\"," +
432 # "\"position\":\{" +
433 # "\"pos_start\":1,\"pos_end\":2," +
434 # "\"line_start\":3,\"line_end\":4," +
435 # "\"col_start\":5,\"col_end\":6" +
436 # "\},\"message\":\"foo\"\}"
439 return "\{\"error\":\"JsonParseError\"," +
440 "\"position\":{position.to_json}," +
441 "\"message\":{message.to_json}\}"
448 # Get the JSON representation of `self`.
451 # var pos = new Position(1, 2, 3, 4, 5, 6)
452 # assert pos.to_json == "\{" +
453 # "\"pos_start\":1,\"pos_end\":2," +
454 # "\"line_start\":3,\"line_end\":4," +
455 # "\"col_start\":5,\"col_end\":6" +
459 return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
460 "\"line_start\":{line_start},\"line_end\":{line_end}," +
461 "\"col_start\":{col_start},\"col_end\":{col_end}\}"
465 ################################################################################
469 # The represented value.
470 private fun to_nit_object: nullable Jsonable is abstract
473 redef class Nvalue_number
474 redef fun to_nit_object
476 var text = n_number.text
477 if text.chars.has('.') or text.chars.has('e
') or text.chars.has('E
') then return text.to_f
482 redef class Nvalue_string
483 redef fun to_nit_object do return n_string.to_nit_string
486 redef class Nvalue_true
487 redef fun to_nit_object do return true
490 redef class Nvalue_false
491 redef fun to_nit_object do return false
494 redef class Nvalue_null
495 redef fun to_nit_object do return null
499 # The represented string.
500 private fun to_nit_string: String do return text.substring(1, text.length - 2).unescape_json.to_s
503 redef class Nvalue_object
504 redef fun to_nit_object do
505 var obj = new JsonObject
506 var members = n_members
507 if members != null then
508 var pairs = members.pairs
509 for pair in pairs do obj[pair.name] = pair.value
516 # All the key-value pairs.
517 private fun pairs: Array[Npair] is abstract
520 redef class Nmembers_tail
523 var arr = n_members.pairs
529 redef class Nmembers_head
530 redef fun pairs do return [n_pair]
534 # The represented key.
535 private fun name: String do return n_string.to_nit_string
537 # The represented value.
538 private fun value: nullable Jsonable do return n_value.to_nit_object
541 redef class Nvalue_array
542 redef fun to_nit_object
544 var arr = new JsonArray
545 var elements = n_elements
546 if elements != null then
547 var items = elements.items
548 for item in items do arr.add(item.to_nit_object)
554 redef class Nelements
556 private fun items: Array[Nvalue] is abstract
559 redef class Nelements_tail
562 var items = n_elements.items
568 redef class Nelements_head
569 redef fun items do return [n_value]