neo_doxygen: Accept classes in the root namespace.
[nit.git] / lib / neo4j / jsonable.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Introduce base classes and services for JSON handling.
16 module jsonable
17
18 import standard
19 private import json::json_parser
20 private import json::json_lexer
21
22 # Something that can be translated to JSON
23 interface Jsonable
24 # Get the JSON representation of `self`
25 fun to_json: String is abstract
26 end
27
28 redef class String
29 super Jsonable
30
31 redef fun to_json do
32 var res = new FlatBuffer
33 res.add '\"'
34 for i in [0..self.length[ do
35 var char = self[i]
36 if char == '\\' then
37 res.append("\\\\")
38 continue
39 else if char == '\"' then
40 res.append("\\\"")
41 continue
42 else if char == '\/' then
43 res.append("\\/")
44 continue
45 else if char == '\n' then
46 res.append("\\n")
47 continue
48 else if char == '\r' then
49 res.append("\\r")
50 continue
51 else if char == '\t' then
52 res.append("\\t")
53 continue
54 end
55 res.add char
56 end
57 res.add '\"'
58 return res.write_to_string
59 end
60 end
61
62 redef class Int
63 super Jsonable
64
65 redef fun to_json do return self.to_s
66 end
67
68 redef class Float
69 super Jsonable
70
71 redef fun to_json do return self.to_s
72 end
73
74 redef class Bool
75 super Jsonable
76
77 redef fun to_json do return self.to_s
78 end
79
80 # A JSON Object representation that behaves like a `Map`
81 class JsonObject
82 super Jsonable
83 super Map[String, nullable Jsonable]
84
85 private var map = new HashMap[String, nullable Jsonable]
86
87 # Create an empty `JsonObject`
88 #
89 # var obj = new JsonObject
90 # assert obj.is_empty
91 init do end
92
93 # Init the JSON Object from a Nit `Map`
94 #
95 # var map = new HashMap[String, String]
96 # map["foo"] = "bar"
97 # map["goo"] = "baz"
98 # var obj = new JsonObject.from(map)
99 # assert obj.length == 2
100 # assert obj["foo"] == "bar"
101 # assert obj["goo"] == "baz"
102 init from(items: Map[String, nullable Jsonable]) do
103 for k, v in items do map[k] = v
104 end
105
106 redef fun [](key) do return map[key]
107 redef fun []=(key, value) do map[key] = value
108 redef fun clear do map.clear
109 redef fun has_key(key) do return map.has_key(key)
110 redef fun is_empty do return map.is_empty
111 redef fun iterator do return map.iterator
112 redef fun keys do return map.keys
113 redef fun values do return map.values
114 redef fun length do return map.length
115
116 # Advanced query to get a value within `self` or its children.
117 #
118 # A query is composed of the keys to each object seperated by '.'.
119 #
120 # REQUIRE `self.has_key(query)`
121 #
122 # var obj1 = new JsonObject
123 # obj1["baz"] = "foobarbaz"
124 # var obj2 = new JsonObject
125 # obj2["bar"] = obj1
126 # var obj3 = new JsonObject
127 # obj3["foo"] = obj2
128 # assert obj3.get("foo.bar.baz") == "foobarbaz"
129 fun get(query: String): nullable Jsonable do
130 var keys = query.split(".").reversed
131 var key = keys.pop
132
133 assert has_key(key)
134 var node = self[key]
135
136 while not keys.is_empty do
137 key = keys.pop
138 assert node isa JsonObject and node.has_key(key)
139 node = node[key]
140 end
141 return node
142 end
143
144 # Create an empty `JsonObject`
145 #
146 # var obj = new JsonObject
147 # obj["foo"] = "bar"
148 # assert obj.to_json == "\{\"foo\": \"bar\"\}"
149 redef fun to_json do
150 var tpl = new Array[String]
151 tpl.add "\{"
152 var vals = new Array[String]
153 for k, v in self do
154 if v == null then
155 vals.add "{k.to_json}: null"
156 else
157 vals.add "{k.to_json}: {v.to_json}"
158 end
159 end
160 tpl.add vals.join(",")
161 tpl.add "\}"
162 return tpl.join("")
163 end
164
165 redef fun to_s do return to_json
166 end
167
168 # A JSON Array representation that behaves like a `Sequence`
169 class JsonArray
170 super Jsonable
171 super Sequence[nullable Jsonable]
172
173 private var array = new Array[nullable Jsonable]
174
175 init do end
176
177 # init the JSON Array from a Nit `Collection`
178 init from(items: Collection[nullable Jsonable]) do
179 array.add_all(items)
180 end
181
182 redef fun [](key) do return array[key]
183 redef fun []=(key, value) do array[key] = value
184 redef fun clear do array.clear
185 redef fun insert(item, index) do array.insert(item, index)
186 redef fun is_empty do return array.is_empty
187 redef fun iterator do return array.iterator
188 redef fun length do return array.length
189 redef fun pop do return array.pop
190 redef fun push(value) do array.push(value)
191 redef fun remove_at(index) do array.remove_at(index)
192 redef fun shift do return array.shift
193 redef fun unshift(e) do array.unshift(e)
194
195 redef fun to_json do
196 var tpl = new Array[String]
197 tpl.add "["
198 var vals = new Array[String]
199 for v in self do
200 if v == null then
201 vals.add "null"
202 else
203 vals.add v.to_json
204 end
205 end
206 tpl.add vals.join(",")
207 tpl.add "]"
208 return tpl.join("")
209 end
210
211 redef fun to_s do return to_json
212 end
213
214 # An error in JSON format that can be returned by tools using JSON like parsers.
215 #
216 # var error = new JsonError("ErrorCode", "ErrorMessage")
217 # assert error.to_s == "ErrorCode: ErrorMessage"
218 # assert error.to_json == "\{\"error\": \"ErrorCode\", \"message\": \"ErrorMessage\"\}"
219 class JsonError
220 super Jsonable
221
222 # The error code
223 var error: String
224
225 # The error message
226 var message: String
227
228 redef fun to_json do
229 var tpl = new Array[String]
230 tpl.add "\{"
231 tpl.add "\"error\": {error.to_json}, "
232 tpl.add "\"message\": {message.to_json}"
233 tpl.add "\}"
234 return tpl.join("")
235 end
236
237 redef fun to_s do return "{error}: {message}"
238 end
239
240 # Redef parser
241
242 redef class Nvalue
243 private fun to_nit_object: nullable Jsonable is abstract
244 end
245
246 redef class Nvalue_number
247 redef fun to_nit_object
248 do
249 var text = n_number.text
250 if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
251 return text.to_i
252 end
253 end
254
255 redef class Nvalue_string
256 redef fun to_nit_object do return n_string.to_nit_string
257 end
258
259 redef class Nvalue_true
260 redef fun to_nit_object do return true
261 end
262
263 redef class Nvalue_false
264 redef fun to_nit_object do return false
265 end
266
267 redef class Nvalue_null
268 redef fun to_nit_object do return null
269 end
270
271 redef class Nstring
272 # FIXME support \n, etc.
273 fun to_nit_string: String do
274 var res = new FlatBuffer
275 var skip = false
276 for i in [1..text.length-2] do
277 if skip then
278 skip = false
279 continue
280 end
281 var char = text[i]
282 if char == '\\' and i < text.length - 2 then
283 if text[i + 1] == '\\' then
284 res.add('\\')
285 skip = true
286 continue
287 end
288 if text[i + 1] == '\"' then
289 res.add('\"')
290 skip = true
291 continue
292 end
293 if text[i + 1] == '/' then
294 res.add('\/')
295 skip = true
296 continue
297 end
298 if text[i + 1] == 'n' then
299 res.add('\n')
300 skip = true
301 continue
302 end
303 if text[i + 1] == 'r' then
304 res.add('\r')
305 skip = true
306 continue
307 end
308 if text[i + 1] == 't' then
309 res.add('\t')
310 skip = true
311 continue
312 end
313 end
314 res.add char
315 end
316 return res.write_to_string
317 end
318 end
319
320 redef class Nvalue_object
321 redef fun to_nit_object
322 do
323 var obj = new JsonObject
324 var members = n_members
325 if members != null then
326 var pairs = members.pairs
327 for pair in pairs do obj[pair.name] = pair.value
328 end
329 return obj
330 end
331 end
332
333 redef class Nmembers
334 fun pairs: Array[Npair] is abstract
335 end
336
337 redef class Nmembers_tail
338 redef fun pairs
339 do
340 var arr = n_members.pairs
341 arr.add n_pair
342 return arr
343 end
344 end
345
346 redef class Nmembers_head
347 redef fun pairs do return [n_pair]
348 end
349
350 redef class Npair
351 fun name: String do return n_string.to_nit_string
352 fun value: nullable Jsonable do return n_value.to_nit_object
353 end
354
355 redef class Nvalue_array
356 redef fun to_nit_object
357 do
358 var arr = new JsonArray
359 var elements = n_elements
360 if elements != null then
361 var items = elements.items
362 for item in items do arr.add(item.to_nit_object)
363 end
364 return arr
365 end
366 end
367
368 redef class Nelements
369 fun items: Array[Nvalue] is abstract
370 end
371
372 redef class Nelements_tail
373 redef fun items
374 do
375 var items = n_elements.items
376 items.add(n_value)
377 return items
378 end
379 end
380
381 redef class Nelements_head
382 redef fun items do return [n_value]
383 end
384
385 redef class Text
386 # Parse a JSON String as Jsonable entities
387 #
388 # Example with `JsonObject`"
389 #
390 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".to_jsonable
391 # assert obj isa JsonObject
392 # assert obj["foo"] isa JsonObject
393 # assert obj["foo"].as(JsonObject)["bar"] == true
394 #
395 # Example with `JsonArray`
396 #
397 # var arr = "[1, 2, 3]".to_jsonable
398 # assert arr isa JsonArray
399 # assert arr.length == 3
400 # assert arr.first == 1
401 # assert arr.last == 3
402 #
403 # Example with `String`
404 #
405 # var str = "\"foo, bar, baz\"".to_jsonable
406 # assert str isa String
407 # assert str == "foo, bar, baz"
408 #
409 # Malformed JSON input returns a `JsonError` object
410 #
411 # var bad = "\{foo: \"bar\"\}".to_jsonable
412 # assert bad isa JsonError
413 # assert bad.error == "JsonLexerError"
414 fun to_jsonable: nullable Jsonable
415 do
416 var lexer = new Lexer_json(to_s)
417 var parser = new Parser_json
418 var tokens = lexer.lex
419 parser.tokens.add_all(tokens)
420 var root_node = parser.parse
421 if root_node isa NStart then
422 return root_node.n_0.to_nit_object
423 else if root_node isa NLexerError then
424 var pos = root_node.position
425 var msg = "{root_node.message} at {pos or else "<unknown>"} for {root_node}"
426 return new JsonError("JsonLexerError", msg)
427 else if root_node isa NParserError then
428 var pos = root_node.position
429 var msg = "{root_node.message} at {pos or else "<unknown>"} for {root_node}"
430 return new JsonError("JsonParsingError", msg)
431 else abort
432 end
433 end
434