95a76a33334bcec04f1fb46b7aff0083c28bdd5f
[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 the JSON Array from a Nit `Collection`
176 init from(items: Collection[nullable Jsonable]) do
177 array.add_all(items)
178 end
179
180 redef fun [](key) do return array[key]
181 redef fun []=(key, value) do array[key] = value
182 redef fun clear do array.clear
183 redef fun insert(item, index) do array.insert(item, index)
184 redef fun is_empty do return array.is_empty
185 redef fun iterator do return array.iterator
186 redef fun length do return array.length
187 redef fun pop do return array.pop
188 redef fun push(value) do array.push(value)
189 redef fun remove_at(index) do array.remove_at(index)
190 redef fun shift do return array.shift
191 redef fun unshift(e) do array.unshift(e)
192
193 redef fun to_json do
194 var tpl = new Array[String]
195 tpl.add "["
196 var vals = new Array[String]
197 for v in self do
198 if v == null then
199 vals.add "null"
200 else
201 vals.add v.to_json
202 end
203 end
204 tpl.add vals.join(",")
205 tpl.add "]"
206 return tpl.join("")
207 end
208
209 redef fun to_s do return to_json
210 end
211
212 # An error in JSON format that can be returned by tools using JSON like parsers.
213 #
214 # var error = new JsonError("ErrorCode", "ErrorMessage")
215 # assert error.to_s == "ErrorCode: ErrorMessage"
216 # assert error.to_json == "\{\"error\": \"ErrorCode\", \"message\": \"ErrorMessage\"\}"
217 class JsonError
218 super Jsonable
219
220 # The error code
221 var error: String
222
223 # The error message
224 var message: String
225
226 redef fun to_json do
227 var tpl = new Array[String]
228 tpl.add "\{"
229 tpl.add "\"error\": {error.to_json}, "
230 tpl.add "\"message\": {message.to_json}"
231 tpl.add "\}"
232 return tpl.join("")
233 end
234
235 redef fun to_s do return "{error}: {message}"
236 end
237
238 # Redef parser
239
240 redef class Nvalue
241 private fun to_nit_object: nullable Jsonable is abstract
242 end
243
244 redef class Nvalue_number
245 redef fun to_nit_object
246 do
247 var text = n_number.text
248 if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
249 return text.to_i
250 end
251 end
252
253 redef class Nvalue_string
254 redef fun to_nit_object do return n_string.to_nit_string
255 end
256
257 redef class Nvalue_true
258 redef fun to_nit_object do return true
259 end
260
261 redef class Nvalue_false
262 redef fun to_nit_object do return false
263 end
264
265 redef class Nvalue_null
266 redef fun to_nit_object do return null
267 end
268
269 redef class Nstring
270 # FIXME support \n, etc.
271 fun to_nit_string: String do
272 var res = new FlatBuffer
273 var skip = false
274 for i in [1..text.length-2] do
275 if skip then
276 skip = false
277 continue
278 end
279 var char = text[i]
280 if char == '\\' and i < text.length - 2 then
281 if text[i + 1] == '\\' then
282 res.add('\\')
283 skip = true
284 continue
285 end
286 if text[i + 1] == '\"' then
287 res.add('\"')
288 skip = true
289 continue
290 end
291 if text[i + 1] == '/' then
292 res.add('\/')
293 skip = true
294 continue
295 end
296 if text[i + 1] == 'n' then
297 res.add('\n')
298 skip = true
299 continue
300 end
301 if text[i + 1] == 'r' then
302 res.add('\r')
303 skip = true
304 continue
305 end
306 if text[i + 1] == 't' then
307 res.add('\t')
308 skip = true
309 continue
310 end
311 end
312 res.add char
313 end
314 return res.write_to_string
315 end
316 end
317
318 redef class Nvalue_object
319 redef fun to_nit_object
320 do
321 var obj = new JsonObject
322 var members = n_members
323 if members != null then
324 var pairs = members.pairs
325 for pair in pairs do obj[pair.name] = pair.value
326 end
327 return obj
328 end
329 end
330
331 redef class Nmembers
332 fun pairs: Array[Npair] is abstract
333 end
334
335 redef class Nmembers_tail
336 redef fun pairs
337 do
338 var arr = n_members.pairs
339 arr.add n_pair
340 return arr
341 end
342 end
343
344 redef class Nmembers_head
345 redef fun pairs do return [n_pair]
346 end
347
348 redef class Npair
349 fun name: String do return n_string.to_nit_string
350 fun value: nullable Jsonable do return n_value.to_nit_object
351 end
352
353 redef class Nvalue_array
354 redef fun to_nit_object
355 do
356 var arr = new JsonArray
357 var elements = n_elements
358 if elements != null then
359 var items = elements.items
360 for item in items do arr.add(item.to_nit_object)
361 end
362 return arr
363 end
364 end
365
366 redef class Nelements
367 fun items: Array[Nvalue] is abstract
368 end
369
370 redef class Nelements_tail
371 redef fun items
372 do
373 var items = n_elements.items
374 items.add(n_value)
375 return items
376 end
377 end
378
379 redef class Nelements_head
380 redef fun items do return [n_value]
381 end
382
383 redef class Text
384 # Parse a JSON String as Jsonable entities
385 #
386 # Example with `JsonObject`"
387 #
388 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".to_jsonable
389 # assert obj isa JsonObject
390 # assert obj["foo"] isa JsonObject
391 # assert obj["foo"].as(JsonObject)["bar"] == true
392 #
393 # Example with `JsonArray`
394 #
395 # var arr = "[1, 2, 3]".to_jsonable
396 # assert arr isa JsonArray
397 # assert arr.length == 3
398 # assert arr.first == 1
399 # assert arr.last == 3
400 #
401 # Example with `String`
402 #
403 # var str = "\"foo, bar, baz\"".to_jsonable
404 # assert str isa String
405 # assert str == "foo, bar, baz"
406 #
407 # Malformed JSON input returns a `JsonError` object
408 #
409 # var bad = "\{foo: \"bar\"\}".to_jsonable
410 # assert bad isa JsonError
411 # assert bad.error == "JsonLexerError"
412 fun to_jsonable: nullable Jsonable
413 do
414 var lexer = new Lexer_json(to_s)
415 var parser = new Parser_json
416 var tokens = lexer.lex
417 parser.tokens.add_all(tokens)
418 var root_node = parser.parse
419 if root_node isa NStart then
420 return root_node.n_0.to_nit_object
421 else if root_node isa NLexerError then
422 var pos = root_node.position
423 var msg = "{root_node.message} at {pos or else "<unknown>"} for {root_node}"
424 return new JsonError("JsonLexerError", msg)
425 else if root_node isa NParserError then
426 var pos = root_node.position
427 var msg = "{root_node.message} at {pos or else "<unknown>"} for {root_node}"
428 return new JsonError("JsonParsingError", msg)
429 else abort
430 end
431 end
432