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