e333b85fdd55833704c47db72143942d1fb3dcef
[nit.git] / lib / json / static.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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>
6 #
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
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
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.
18
19 # Static interface to get Nit objects from a Json string.
20 #
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`).
24 module static
25
26 import error
27 private import json_parser
28 private import json_lexer
29
30 # Something that can be translated to JSON.
31 interface Jsonable
32 # Encode `self` in JSON.
33 #
34 # This is a recursive method which can be refined by any subclasses.
35 # To write any `Serializable` object to JSON, see `serialize_to_json`.
36 #
37 # SEE: `append_json`
38 fun to_json: String is abstract
39
40 # Use `append_json` to implement `to_json`.
41 #
42 # Therefore, one that redefine `append_json` may use the following
43 # redefinition to link `to_json` and `append_json`:
44 #
45 # ~~~nitish
46 # redef fun to_json do return to_json_by_append
47 # ~~~
48 #
49 # Note: This is not the default implementation of `to_json` in order to
50 # avoid cyclic references between `append_json` and `to_json` when none are
51 # implemented.
52 protected fun to_json_by_append: String do
53 var buffer = new FlatBuffer
54 append_json(buffer)
55 return buffer.to_s
56 end
57
58 # Append the JSON representation of `self` to the specified buffer.
59 #
60 # SEE: `to_json`
61 fun append_json(buffer: Buffer) do buffer.append(to_json)
62
63 # Pretty print JSON string.
64 #
65 # ~~~
66 # var obj = new JsonObject
67 # obj["foo"] = 1
68 # obj["bar"] = true
69 # var arr = new JsonArray
70 # arr.add 2
71 # arr.add false
72 # arr.add "baz"
73 # obj["baz"] = arr
74 # var res = obj.to_pretty_json
75 # var exp = """{
76 # \t"foo": 1,
77 # \t"bar": true,
78 # \t"baz": [2, false, "baz"]
79 # }\n"""
80 # assert res == exp
81 # ~~~
82 fun to_pretty_json: String do
83 var res = new FlatBuffer
84 pretty_json_visit(res, 0)
85 res.add '\n'
86 return res.to_s
87 end
88
89 private fun pretty_json_visit(buffer: FlatBuffer, indent: Int) is abstract
90 end
91
92 redef class Text
93 super Jsonable
94
95 # Removes JSON-escaping if necessary in a JSON string
96 #
97 # assert "\\\"string\\uD83D\\uDE02\\\"".unescape_json == "\"string😂\""
98 fun unescape_json: Text do
99 if not json_need_escape then return self
100 return self.json_to_nit_string
101 end
102
103 # Does `self` need treatment from JSON to Nit ?
104 #
105 # i.e. is there at least one `\` character in it ?
106 #
107 # assert not "string".json_need_escape
108 # assert "\\\"string\\\"".json_need_escape
109 protected fun json_need_escape: Bool do return has('\\')
110
111 redef fun append_json(buffer) do
112 buffer.add '\"'
113 for i in [0 .. self.length[ do
114 var char = self[i]
115 if char == '\\' then
116 buffer.append "\\\\"
117 else if char == '\"' then
118 buffer.append "\\\""
119 else if char < ' ' then
120 if char == '\n' then
121 buffer.append "\\n"
122 else if char == '\r' then
123 buffer.append "\\r"
124 else if char == '\t' then
125 buffer.append "\\t"
126 else
127 buffer.append char.escape_to_utf16
128 end
129 else
130 buffer.add char
131 end
132 end
133 buffer.add '\"'
134 end
135
136 # Escapes `self` from a JSON string to a Nit string
137 #
138 # assert "\\\"string\\\"".json_to_nit_string == "\"string\""
139 # assert "\\nEscape\\t\\n".json_to_nit_string == "\nEscape\t\n"
140 # assert "\\u0041zu\\uD800\\uDFD3".json_to_nit_string == "Azu𐏓"
141 protected fun json_to_nit_string: String do
142 var res = new FlatBuffer.with_capacity(bytelen)
143 var i = 0
144 var ln = self.length
145 while i < ln do
146 var char = self[i]
147 if char == '\\' then
148 i += 1
149 char = self[i]
150 if char == 'b' then
151 char = 0x08.code_point
152 else if char == 'f' then
153 char = 0x0C.code_point
154 else if char == 'n' then
155 char = '\n'
156 else if char == 'r' then
157 char = '\r'
158 else if char == 't' then
159 char = '\t'
160 else if char == 'u' then
161 var u16_esc = from_utf16_digit(i + 1)
162 char = u16_esc.code_point
163 if char.is_surrogate and i + 10 < ln then
164 if self[i + 5] == '\\' and self[i + 6] == 'u' then
165 u16_esc <<= 16
166 u16_esc += from_utf16_digit(i + 7)
167 char = u16_esc.from_utf16_surr.code_point
168 i += 6
169 else
170 char = 0xFFFD.code_point
171 end
172 end
173 i += 4
174 end
175 # `"`, `/` or `\` => Keep `char` as-is.
176 end
177 res.add char
178 i += 1
179 end
180 return res.to_s
181 end
182
183
184 # Encode `self` in JSON.
185 #
186 # ~~~
187 # assert "\t\"http://example.com\"\r\n\0\\".to_json ==
188 # "\"\\t\\\"http://example.com\\\"\\r\\n\\u0000\\\\\""
189 # ~~~
190 redef fun to_json do
191 var b = new FlatBuffer.with_capacity(bytelen)
192 append_json(b)
193 return b.to_s
194 end
195
196 # Parse `self` as JSON.
197 #
198 # If `self` is not a valid JSON document or contains an unsupported escape
199 # sequence, return a `JSONParseError`.
200 #
201 # Example with `JsonObject`:
202 #
203 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
204 # assert obj isa JsonObject
205 # assert obj["foo"] isa JsonObject
206 # assert obj["foo"].as(JsonObject)["bar"] == true
207 #
208 # Example with `JsonArray`:
209 #
210 # var arr = "[1, 2, 3]".parse_json
211 # assert arr isa JsonArray
212 # assert arr.length == 3
213 # assert arr.first == 1
214 # assert arr.last == 3
215 #
216 # Example with `String`:
217 #
218 # var str = "\"foo, bar, baz\"".parse_json
219 # assert str isa String
220 # assert str == "foo, bar, baz"
221 #
222 # Example of a syntaxic error:
223 #
224 # var bad = "\{foo: \"bar\"\}".parse_json
225 # assert bad isa JsonParseError
226 # assert bad.position.col_start == 2
227 fun parse_json: nullable Jsonable do
228 var lexer = new Lexer_json(to_s)
229 var parser = new Parser_json
230 var tokens = lexer.lex
231 parser.tokens.add_all(tokens)
232 var root_node = parser.parse
233 if root_node isa NStart then
234 return root_node.n_0.to_nit_object
235 else if root_node isa NError then
236 return new JsonParseError(root_node.message, root_node.position)
237 else abort
238 end
239 end
240
241 redef class FlatText
242 redef fun json_need_escape do
243 var its = items
244 for i in [first_byte .. last_byte] do
245 if its[i] == 0x5Cu8 then return true
246 end
247 return false
248 end
249 end
250
251 redef class Buffer
252
253 # Append the JSON representation of `jsonable` to `self`.
254 #
255 # Append `"null"` for `null`.
256 private fun append_json_of(jsonable: nullable Jsonable) do
257 if jsonable isa Jsonable then
258 append jsonable.to_json
259 else
260 append "null"
261 end
262 end
263 end
264
265 redef class Int
266 super Jsonable
267
268 # Encode `self` in JSON.
269 #
270 # assert 0.to_json == "0"
271 # assert (-42).to_json == "-42"
272 redef fun to_json do return self.to_s
273 end
274
275 redef class Float
276 super Jsonable
277
278 # Encode `self` in JSON.
279 #
280 # Note: Because this method use `to_s`, it may lose precision.
281 #
282 # ~~~
283 # # Will not work as expected.
284 # # assert (-0.0).to_json == "-0.0"
285 #
286 # assert (.5).to_json == "0.5"
287 # assert (0.0).to_json == "0.0"
288 # ~~~
289 redef fun to_json do return self.to_s
290 end
291
292 redef class Bool
293 super Jsonable
294
295 # Encode `self` in JSON.
296 #
297 # assert true.to_json == "true"
298 # assert false.to_json == "false"
299 redef fun to_json do return self.to_s
300 end
301
302 # A map that can be translated into a JSON object.
303 interface JsonMapRead[K: String, V: nullable Jsonable]
304 super MapRead[K, V]
305 super Jsonable
306
307 redef fun append_json(buffer) do
308 buffer.append "\{"
309 var it = iterator
310 if it.is_ok then
311 append_json_entry(it, buffer)
312 while it.is_ok do
313 buffer.append ","
314 append_json_entry(it, buffer)
315 end
316 end
317 it.finish
318 buffer.append "\}"
319 end
320
321 # Encode `self` in JSON.
322 #
323 # var obj = new JsonObject
324 # obj["foo"] = "bar"
325 # assert obj.to_json == "\{\"foo\":\"bar\"\}"
326 # obj = new JsonObject
327 # obj["baz"] = null
328 # assert obj.to_json == "\{\"baz\":null\}"
329 redef fun to_json do return to_json_by_append
330
331 redef fun pretty_json_visit(buffer, indent) do
332 buffer.append "\{\n"
333 indent += 1
334 var i = 0
335 for k, v in self do
336 buffer.append "\t" * indent
337 buffer.append "\"{k}\": "
338 if v isa JsonObject or v isa JsonArray then
339 v.pretty_json_visit(buffer, indent)
340 else
341 buffer.append v.to_json
342 end
343 if i < length - 1 then
344 buffer.append ","
345 end
346 buffer.append "\n"
347 i += 1
348 end
349 indent -= 1
350 buffer.append "\t" * indent
351 buffer.append "\}"
352 end
353
354 private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
355 buffer: Buffer) do
356 buffer.append iterator.key.to_json
357 buffer.append ":"
358 buffer.append_json_of(iterator.item)
359 iterator.next
360 end
361 end
362
363 # A JSON Object.
364 class JsonObject
365 super JsonMapRead[String, nullable Jsonable]
366 super HashMap[String, nullable Jsonable]
367 end
368
369 # A sequence that can be translated into a JSON array.
370 class JsonSequenceRead[E: nullable Jsonable]
371 super Jsonable
372 super SequenceRead[E]
373
374 redef fun append_json(buffer) do
375 buffer.append "["
376 var it = iterator
377 if it.is_ok then
378 append_json_entry(it, buffer)
379 while it.is_ok do
380 buffer.append ","
381 append_json_entry(it, buffer)
382 end
383 end
384 it.finish
385 buffer.append "]"
386 end
387
388 # Encode `self` in JSON.
389 #
390 # var arr = new JsonArray.with_items("foo", null)
391 # assert arr.to_json == "[\"foo\",null]"
392 # arr.pop
393 # assert arr.to_json =="[\"foo\"]"
394 # arr.pop
395 # assert arr.to_json =="[]"
396 redef fun to_json do return to_json_by_append
397
398 redef fun pretty_json_visit(buffer, indent) do
399 buffer.append "\["
400 var i = 0
401 for v in self do
402 if v isa JsonObject or v isa JsonArray then
403 v.pretty_json_visit(buffer, indent)
404 else
405 buffer.append v.to_json
406 end
407 if i < length - 1 then buffer.append ", "
408 i += 1
409 end
410 buffer.append "\]"
411 end
412
413 private fun append_json_entry(iterator: Iterator[nullable Jsonable],
414 buffer: Buffer) do
415 buffer.append_json_of(iterator.item)
416 iterator.next
417 end
418 end
419
420 # A JSON array.
421 class JsonArray
422 super JsonSequenceRead[nullable Jsonable]
423 super Array[nullable Jsonable]
424 end
425
426 redef class JsonParseError
427 super Jsonable
428
429 # Get the JSON representation of `self`.
430 #
431 # ~~~
432 # var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
433 # assert err.to_json == "\{\"error\":\"JsonParseError\"," +
434 # "\"position\":\{" +
435 # "\"pos_start\":1,\"pos_end\":2," +
436 # "\"line_start\":3,\"line_end\":4," +
437 # "\"col_start\":5,\"col_end\":6" +
438 # "\},\"message\":\"foo\"\}"
439 # ~~~
440 redef fun to_json do
441 return "\{\"error\":\"JsonParseError\"," +
442 "\"position\":{position.to_json}," +
443 "\"message\":{message.to_json}\}"
444 end
445
446 redef fun pretty_json_visit(buf, indents) do
447 buf.clear
448 buf.append(to_json)
449 end
450 end
451
452 redef class Position
453 super Jsonable
454
455 # Get the JSON representation of `self`.
456 #
457 # ~~~
458 # var pos = new Position(1, 2, 3, 4, 5, 6)
459 # assert pos.to_json == "\{" +
460 # "\"pos_start\":1,\"pos_end\":2," +
461 # "\"line_start\":3,\"line_end\":4," +
462 # "\"col_start\":5,\"col_end\":6" +
463 # "\}"
464 # ~~~
465 redef fun to_json do
466 return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
467 "\"line_start\":{line_start},\"line_end\":{line_end}," +
468 "\"col_start\":{col_start},\"col_end\":{col_end}\}"
469 end
470 end
471
472 ################################################################################
473 # Redef parser
474
475 redef class Nvalue
476 # The represented value.
477 private fun to_nit_object: nullable Jsonable is abstract
478 end
479
480 redef class Nvalue_number
481 redef fun to_nit_object
482 do
483 var text = n_number.text
484 if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
485 return text.to_i
486 end
487 end
488
489 redef class Nvalue_string
490 redef fun to_nit_object do return n_string.to_nit_string
491 end
492
493 redef class Nvalue_true
494 redef fun to_nit_object do return true
495 end
496
497 redef class Nvalue_false
498 redef fun to_nit_object do return false
499 end
500
501 redef class Nvalue_null
502 redef fun to_nit_object do return null
503 end
504
505 redef class Nstring
506 # The represented string.
507 private fun to_nit_string: String do return text.substring(1, text.length - 2).unescape_json.to_s
508 end
509
510 redef class Nvalue_object
511 redef fun to_nit_object do
512 var obj = new JsonObject
513 var members = n_members
514 if members != null then
515 var pairs = members.pairs
516 for pair in pairs do obj[pair.name] = pair.value
517 end
518 return obj
519 end
520 end
521
522 redef class Nmembers
523 # All the key-value pairs.
524 private fun pairs: Array[Npair] is abstract
525 end
526
527 redef class Nmembers_tail
528 redef fun pairs
529 do
530 var arr = n_members.pairs
531 arr.add n_pair
532 return arr
533 end
534 end
535
536 redef class Nmembers_head
537 redef fun pairs do return [n_pair]
538 end
539
540 redef class Npair
541 # The represented key.
542 private fun name: String do return n_string.to_nit_string
543
544 # The represented value.
545 private fun value: nullable Jsonable do return n_value.to_nit_object
546 end
547
548 redef class Nvalue_array
549 redef fun to_nit_object
550 do
551 var arr = new JsonArray
552 var elements = n_elements
553 if elements != null then
554 var items = elements.items
555 for item in items do arr.add(item.to_nit_object)
556 end
557 return arr
558 end
559 end
560
561 redef class Nelements
562 # All the items.
563 private fun items: Array[Nvalue] is abstract
564 end
565
566 redef class Nelements_tail
567 redef fun items
568 do
569 var items = n_elements.items
570 items.add(n_value)
571 return items
572 end
573 end
574
575 redef class Nelements_head
576 redef fun items do return [n_value]
577 end