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