json: Factorize the trivial implementation of `to_json`.
[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 # SEE: `append_json`
35 fun to_json: String is abstract
36
37 # Use `append_json` to implement `to_json`.
38 #
39 # Therefore, one that redefine `append_json` may use the following
40 # redefinition to link `to_json` and `append_json`:
41 #
42 # ~~~nitish
43 # redef fun to_json do return to_json_by_append
44 # ~~~
45 #
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
48 # implemented.
49 protected fun to_json_by_append: String do
50 var buffer = new RopeBuffer
51 append_json(buffer)
52 return buffer.write_to_string
53 end
54
55 # Append the JSON representation of `self` to the specified buffer.
56 #
57 # SEE: `to_json`
58 fun append_json(buffer: Buffer) do buffer.append(to_json)
59 end
60
61 redef class Text
62 super Jsonable
63
64 redef fun append_json(buffer) do
65 buffer.add '\"'
66 for i in [0..self.length[ do
67 var char = self[i]
68 if char == '\\' then
69 buffer.append "\\\\"
70 else if char == '\"' then
71 buffer.append "\\\""
72 else if char == '\/' then
73 buffer.append "\\/"
74 else if char < 16.ascii then
75 if char == '\n' then
76 buffer.append "\\n"
77 else if char == '\r' then
78 buffer.append "\\r"
79 else if char == '\t' then
80 buffer.append "\\t"
81 else if char == 0x0C.ascii then
82 buffer.append "\\f"
83 else if char == 0x08.ascii then
84 buffer.append "\\b"
85 else
86 buffer.append "\\u000{char.ascii.to_hex}"
87 end
88 else if char < ' ' then
89 buffer.append "\\u00{char.ascii.to_hex}"
90 else
91 buffer.add char
92 end
93 end
94 buffer.add '\"'
95 end
96
97 # Encode `self` in JSON.
98 #
99 # assert "\t\"http://example.com\"\r\n\0\\".to_json ==
100 # "\"\\t\\\"http:\\/\\/example.com\\\"\\r\\n\\u0000\\\\\""
101 redef fun to_json do return to_json_by_append
102
103 # Parse `self` as JSON.
104 #
105 # If `self` is not a valid JSON document or contains an unsupported escape
106 # sequence, return a `JSONParseError`.
107 #
108 # Example with `JsonObject`:
109 #
110 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
111 # assert obj isa JsonObject
112 # assert obj["foo"] isa JsonObject
113 # assert obj["foo"].as(JsonObject)["bar"] == true
114 #
115 # Example with `JsonArray`:
116 #
117 # var arr = "[1, 2, 3]".parse_json
118 # assert arr isa JsonArray
119 # assert arr.length == 3
120 # assert arr.first == 1
121 # assert arr.last == 3
122 #
123 # Example with `String`:
124 #
125 # var str = "\"foo, bar, baz\"".parse_json
126 # assert str isa String
127 # assert str == "foo, bar, baz"
128 #
129 # Example of a syntaxic error:
130 #
131 # var bad = "\{foo: \"bar\"\}".parse_json
132 # assert bad isa JsonParseError
133 # assert bad.position.col_start == 2
134 fun parse_json: nullable Jsonable do
135 var lexer = new Lexer_json(to_s)
136 var parser = new Parser_json
137 var tokens = lexer.lex
138 parser.tokens.add_all(tokens)
139 var root_node = parser.parse
140 if root_node isa NStart then
141 return root_node.n_0.to_nit_object
142 else if root_node isa NError then
143 return new JsonParseError(root_node.message, root_node.position)
144 else abort
145 end
146 end
147
148 redef class Buffer
149
150 # Append the JSON representation of `jsonable` to `self`.
151 #
152 # Append `"null"` for `null`.
153 private fun append_json_of(jsonable: nullable Jsonable) do
154 if jsonable isa Jsonable then
155 append jsonable.to_json
156 else
157 append "null"
158 end
159 end
160 end
161
162 redef class Int
163 super Jsonable
164
165 # Encode `self` in JSON.
166 #
167 # assert 0.to_json == "0"
168 # assert (-42).to_json == "-42"
169 redef fun to_json do return self.to_s
170 end
171
172 redef class Float
173 super Jsonable
174
175 # Encode `self` in JSON.
176 #
177 # Note: Because this method use `to_s`, it may lose precision.
178 #
179 # ~~~
180 # # Will not work as expected.
181 # # assert (-0.0).to_json == "-0.0"
182 #
183 # assert (.5).to_json == "0.5"
184 # assert (0.0).to_json == "0.0"
185 # ~~~
186 redef fun to_json do return self.to_s
187 end
188
189 redef class Bool
190 super Jsonable
191
192 # Encode `self` in JSON.
193 #
194 # assert true.to_json == "true"
195 # assert false.to_json == "false"
196 redef fun to_json do return self.to_s
197 end
198
199 # A map that can be translated into a JSON object.
200 interface JsonMapRead[K: String, V: nullable Jsonable]
201 super MapRead[K, V]
202 super Jsonable
203
204 redef fun append_json(buffer) do
205 buffer.append "\{"
206 var it = iterator
207 if it.is_ok then
208 append_json_entry(it, buffer)
209 while it.is_ok do
210 buffer.append ","
211 append_json_entry(it, buffer)
212 end
213 end
214 it.finish
215 buffer.append "\}"
216 end
217
218 # Encode `self` in JSON.
219 #
220 # var obj = new JsonObject
221 # obj["foo"] = "bar"
222 # assert obj.to_json == "\{\"foo\":\"bar\"\}"
223 # obj = new JsonObject
224 # obj["baz"] = null
225 # assert obj.to_json == "\{\"baz\":null\}"
226 redef fun to_json do return to_json_by_append
227
228 private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
229 buffer: Buffer) do
230 buffer.append iterator.key.to_json
231 buffer.append ":"
232 buffer.append_json_of(iterator.item)
233 iterator.next
234 end
235 end
236
237 # A JSON Object.
238 class JsonObject
239 super JsonMapRead[String, nullable Jsonable]
240 super HashMap[String, nullable Jsonable]
241 end
242
243 # A sequence that can be translated into a JSON array.
244 class JsonSequenceRead[E: nullable Jsonable]
245 super Jsonable
246 super SequenceRead[E]
247
248 redef fun append_json(buffer) do
249 buffer.append "["
250 var it = iterator
251 if it.is_ok then
252 append_json_entry(it, buffer)
253 while it.is_ok do
254 buffer.append ","
255 append_json_entry(it, buffer)
256 end
257 end
258 it.finish
259 buffer.append "]"
260 end
261
262 # Encode `self` in JSON.
263 #
264 # var arr = new JsonArray.with_items("foo", null)
265 # assert arr.to_json == "[\"foo\",null]"
266 # arr.pop
267 # assert arr.to_json =="[\"foo\"]"
268 # arr.pop
269 # assert arr.to_json =="[]"
270 redef fun to_json do return to_json_by_append
271
272 private fun append_json_entry(iterator: Iterator[nullable Jsonable],
273 buffer: Buffer) do
274 buffer.append_json_of(iterator.item)
275 iterator.next
276 end
277 end
278
279 # A JSON array.
280 class JsonArray
281 super JsonSequenceRead[nullable Jsonable]
282 super Array[nullable Jsonable]
283 end
284
285 redef class JsonParseError
286 super Jsonable
287
288 # Get the JSON representation of `self`.
289 #
290 # var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
291 # assert err.to_json == "\{\"error\":\"JsonParseError\"," +
292 # "\"position\":\{" +
293 # "\"pos_start\":1,\"pos_end\":2," +
294 # "\"line_start\":3,\"line_end\":4," +
295 # "\"col_start\":5,\"col_end\":6" +
296 # "\},\"message\":\"foo\"\}"
297 redef fun to_json do
298 return "\{\"error\":\"JsonParseError\"," +
299 "\"position\":{position.to_json}," +
300 "\"message\":{message.to_json}\}"
301 end
302 end
303
304 redef class Position
305 super Jsonable
306
307 # Get the JSON representation of `self`.
308 #
309 # var pos = new Position(1, 2, 3, 4, 5, 6)
310 # assert pos.to_json == "\{" +
311 # "\"pos_start\":1,\"pos_end\":2," +
312 # "\"line_start\":3,\"line_end\":4," +
313 # "\"col_start\":5,\"col_end\":6" +
314 # "\}"
315 redef fun to_json do
316 return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
317 "\"line_start\":{line_start},\"line_end\":{line_end}," +
318 "\"col_start\":{col_start},\"col_end\":{col_end}\}"
319 end
320 end
321
322 ################################################################################
323 # Redef parser
324
325 redef class Nvalue
326 # The represented value.
327 private fun to_nit_object: nullable Jsonable is abstract
328 end
329
330 redef class Nvalue_number
331 redef fun to_nit_object
332 do
333 var text = n_number.text
334 if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
335 return text.to_i
336 end
337 end
338
339 redef class Nvalue_string
340 redef fun to_nit_object do return n_string.to_nit_string
341 end
342
343 redef class Nvalue_true
344 redef fun to_nit_object do return true
345 end
346
347 redef class Nvalue_false
348 redef fun to_nit_object do return false
349 end
350
351 redef class Nvalue_null
352 redef fun to_nit_object do return null
353 end
354
355 redef class Nstring
356 # The represented string.
357 private fun to_nit_string: String do
358 var res = new FlatBuffer
359 var i = 1
360 while i < text.length - 1 do
361 var char = text[i]
362 if char == '\\' then
363 i += 1
364 char = text[i]
365 if char == 'b' then
366 char = 0x08.ascii
367 else if char == 'f' then
368 char = 0x0C.ascii
369 else if char == 'n' then
370 char = '\n'
371 else if char == 'r' then
372 char = '\r'
373 else if char == 't' then
374 char = '\t'
375 else if char == 'u' then
376 var code = text.substring(i + 1, 4).to_hex
377 # TODO UTF-16 escaping is not supported yet.
378 if code >= 128 then
379 char = '?'
380 else
381 char = code.ascii
382 end
383 i += 4
384 end
385 # `"`, `/` or `\` => Keep `char` as-is.
386 end
387 res.add char
388 i += 1
389 end
390 return res.write_to_string
391 end
392 end
393
394 redef class Nvalue_object
395 redef fun to_nit_object do
396 var obj = new JsonObject
397 var members = n_members
398 if members != null then
399 var pairs = members.pairs
400 for pair in pairs do obj[pair.name] = pair.value
401 end
402 return obj
403 end
404 end
405
406 redef class Nmembers
407 # All the key-value pairs.
408 private fun pairs: Array[Npair] is abstract
409 end
410
411 redef class Nmembers_tail
412 redef fun pairs
413 do
414 var arr = n_members.pairs
415 arr.add n_pair
416 return arr
417 end
418 end
419
420 redef class Nmembers_head
421 redef fun pairs do return [n_pair]
422 end
423
424 redef class Npair
425 # The represented key.
426 private fun name: String do return n_string.to_nit_string
427
428 # The represented value.
429 private fun value: nullable Jsonable do return n_value.to_nit_object
430 end
431
432 redef class Nvalue_array
433 redef fun to_nit_object
434 do
435 var arr = new JsonArray
436 var elements = n_elements
437 if elements != null then
438 var items = elements.items
439 for item in items do arr.add(item.to_nit_object)
440 end
441 return arr
442 end
443 end
444
445 redef class Nelements
446 # All the items.
447 private fun items: Array[Nvalue] is abstract
448 end
449
450 redef class Nelements_tail
451 redef fun items
452 do
453 var items = n_elements.items
454 items.add(n_value)
455 return items
456 end
457 end
458
459 redef class Nelements_head
460 redef fun items do return [n_value]
461 end