Merge: curl: basic Unix domain socket support
[nit.git] / lib / json / dynamic.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Dynamic interface to read values from JSON strings
18 #
19 # Most services are in `JsonValue`, which is created by `Text::to_json_value`.
20 module dynamic
21
22 import error
23 private import static
24
25 redef class Text
26 # Parse `self` to a `JsonValue`
27 fun to_json_value: JsonValue do return new JsonValue(parse_json)
28 end
29
30 # Dynamic wrapper of a JSON value, created by `Text::to_json_value`
31 #
32 # Provides high-level services to explore JSON objects with minimal overhead
33 # dealing with static types. Use this class to manipulate the JSON data first
34 # and check for errors only before using the resulting data.
35 #
36 # For example, given:
37 # ~~~
38 # var json_src = """
39 # {
40 # "i": 123,
41 # "m": {
42 # "t": true,
43 # "f": false
44 # },
45 # "a": ["zero", "one", "two"]
46 # }"""
47 # var json_value = json_src.to_json_value # Parse src to a `JsonValue`
48 # ~~~
49 #
50 # Access array or map values using their indices.
51 # ~~~
52 # var target_int = json_value["i"]
53 # assert target_int.is_int # Check for error and expected type
54 # assert target_int.to_i == 123 # Use the value
55 # ~~~
56 #
57 # Use `get` to reach a value nested in multiple objects.
58 # ~~~
59 # var target_str = json_value.get("a.0")
60 # assert target_str.is_string # Check for error and expected type
61 # assert target_str.to_s == "zero" # Use the value
62 # ~~~
63 #
64 # This API is useful for scripts and when you need only a few values from a
65 # JSON object. To access many values or check the conformity of the JSON
66 # beforehand, use the `json` serialization services.
67 class JsonValue
68
69 # The wrapped JSON value.
70 var value: nullable Object
71
72 # Is this value null?
73 #
74 # assert "null".to_json_value.is_null
75 # assert not "123".to_json_value.is_null
76 fun is_null: Bool do return value == null
77
78 # Is this value an integer?
79 #
80 # assert "123".to_json_value.is_int
81 # assert not "1.23".to_json_value.is_int
82 # assert not "\"str\"".to_json_value.is_int
83 fun is_int: Bool do return value isa Int
84
85 # Get this value as a `Int`
86 #
87 # require: `self.is_numeric`
88 #
89 # assert "-10".to_json_value.to_i == -10
90 # assert "123".to_json_value.to_i == 123
91 # assert "123.456".to_json_value.to_i == 123
92 fun to_i: Int
93 do
94 var value = value
95 assert value isa Numeric
96 return value.to_i
97 end
98
99 # Is this value a float?
100 #
101 # assert "0.0".to_json_value.is_float
102 # assert "123.456".to_json_value.is_float
103 # assert not "123".to_json_value.is_float
104 fun is_float: Bool do return value isa Float
105
106 # Get this value as a `Float`
107 #
108 # require: `self.is_numeric`
109 #
110 # assert "0.0".to_json_value.to_f == 0.0
111 # assert "123.456".to_json_value.to_f == 123.456
112 # assert "123".to_json_value.to_f == 123.0
113 fun to_f: Float
114 do
115 var value = value
116 assert value isa Numeric
117 return value.to_f
118 end
119
120 # Is the value numeric?
121 #
122 # assert "1.234".to_json_value.is_numeric
123 # assert "1234".to_json_value.is_numeric
124 # assert not "\"str\"".to_json_value.is_numeric
125 # assert not "1.2.3.4".to_json_value.is_numeric
126 fun is_numeric: Bool do return is_int or is_float
127
128 # Get this value as a `Numeric`
129 #
130 # require: `self.is_numeric`
131 #
132 # assert "1.234".to_json_value.to_numeric == 1.234
133 # assert "1234".to_json_value.to_numeric == 1234
134 fun to_numeric: Numeric
135 do
136 if is_int then return to_i
137 return to_f
138 end
139
140 # Is this value a boolean?
141 #
142 # assert "true".to_json_value.is_bool
143 # assert "false".to_json_value.is_bool
144 fun is_bool: Bool do return value isa Bool
145
146 # Get this value as a `Bool`
147 #
148 # require: `self.is_bool`
149 #
150 # assert "true".to_json_value.to_bool
151 # assert not "false".to_json_value.to_bool
152 fun to_bool: Bool do return value.as(Bool)
153
154 # Is this value a string?
155 #
156 # assert "\"str\"".to_json_value.is_string
157 # assert not "123".to_json_value.is_string
158 fun is_string: Bool do return value isa String
159
160 # Get this value as a `String`
161 #
162 # If value is null, return "null", otherwise returns `value.to_s`. It is practical
163 # on most types, except maps which does not have a custom `to_s`.
164 #
165 # assert "\"str\"".to_json_value.to_s == "str"
166 # assert "123".to_json_value.to_s == "123"
167 # assert "true".to_json_value.to_s == "true"
168 # assert "[1, 2, 3]".to_json_value.to_s == "[1,2,3]"
169 redef fun to_s do return (value or else "null").to_s
170
171 ### Objects
172
173 # Is this value a Json object (a map)?
174 #
175 # assert """{"a": 123}""".to_json_value.is_map
176 # assert not "123".to_json_value.is_map
177 fun is_map: Bool do return value isa MapRead[String, nullable Object]
178
179 # Get this value as a `Map[String, JsonValue]`
180 #
181 # require: `self.is_map`
182 fun to_map: Map[String, JsonValue] do
183 var value = value
184 assert value isa MapRead[String, nullable Object]
185
186 var map = new HashMap[String, JsonValue]
187 for k, v in value do map[k] = new JsonValue(v)
188 return map
189 end
190
191 ### Arrays
192
193 # Is this value an array?
194 #
195 # assert "[]".to_json_value.is_array
196 # assert "[1, 2, 3, 4, 5]".to_json_value.is_array
197 # assert "[null, true, false, 0.0, 1, \"str\"]".to_json_value.is_array
198 # assert """["a", "b", "c"]""".to_json_value.is_array
199 fun is_array: Bool do return value isa SequenceRead[nullable Object]
200
201 # Get this value as an `Array[JsonValue]`
202 #
203 # require: `self.is_array`
204 #
205 # assert """["a", "b", "c"]""".to_json_value.to_a.join(", ") == "a, b, c"
206 fun to_a: Array[JsonValue]
207 do
208 var value = value
209 assert value isa SequenceRead[nullable Object]
210
211 var a = new Array[JsonValue]
212 for e in value do a.add(new JsonValue(e))
213 return a
214 end
215
216 ### Error
217
218 # Is this value an error?
219 #
220 # assert "[]".to_json_value[0].is_error
221 # assert "[".to_json_value.is_error
222 # assert not "[]".to_json_value.is_error
223 fun is_error: Bool do return value isa Error
224
225 # Get this value as a `Error`.
226 #
227 # require: `self.is_error`
228 fun to_error: Error do return value.as(Error)
229
230 ### Children access
231
232 # Iterator over the values of the array `self`
233 #
234 # require: `self.is_array`
235 #
236 # var a = new Array[String]
237 # for e in """["a", "b", "c"]""".to_json_value do a.add(e.to_s)
238 # assert a[0] == "a"
239 # assert a[1] == "b"
240 # assert a[2] == "c"
241 fun iterator: Iterator[JsonValue] do return to_a.iterator
242
243 # Get value at index `key` on the array or map `self`
244 #
245 # require: `self.is_array or self.is_map`
246 # require: `self.is_array implies key isa Int`
247 #
248 # assert """{"a": 123}""".to_json_value["a"].to_i == 123
249 # assert """{"123": "a"}""".to_json_value[123].to_s == "a"
250 # assert """{"John Smith": 1980}""".to_json_value["John Smith"].to_i == 1980
251 # assert """{"a": 123}""".to_json_value["b"].is_error
252 #
253 # assert """["a", "b", "c"]""".to_json_value[0].to_s == "a"
254 # assert """["a", "b", "c"]""".to_json_value[3].is_error
255 fun [](key: Object): JsonValue do
256 var value = value
257 var result: nullable Object
258 if is_error then
259 return self
260 else if value isa MapRead[String, nullable Object] then
261 key = key.to_s
262 if value.has_key(key) then
263 result = value[key]
264 else
265 result = new JsonKeyError("Key `{key}` not found.", self, key)
266 end
267 else if value isa SequenceRead[nullable Object] then
268 if key isa Int then
269 if key < value.length and key >= 0 then
270 result = value[key]
271 else
272 result = new JsonKeyError("Index `{key}` out of bounds.",
273 self, key)
274 end
275 else
276 result = new JsonKeyError("Invalid key type. Expecting `Int`. Got `{key.class_name}`.",
277 self, key)
278 end
279 else
280 result = new JsonKeyError("Invalid `[]` access on a `{json_type}` JsonValue.",
281 self, key)
282 end
283 return new JsonValue(result)
284 end
285
286 # Get the value at `query`, a string of map keys and array indices
287 #
288 # The `query` is composed of map keys and array indices separated by "." (by default).
289 # The separator can be set with `sep` to any string.
290 #
291 # Given the following JSON object parsed as a `JsonValue`.
292 # ~~~
293 # var jvalue = """
294 # {
295 # "a": {
296 # "i": 123,
297 # "b": true
298 # },
299 # "b": ["zero", "one", "two"]
300 # }""".to_json_value
301 # ~~~
302 #
303 # Access a value in maps by its key, starting from the key in the root object.
304 # ~~~
305 # assert jvalue.get("a").is_map
306 # assert jvalue.get("a.i").to_i == 123
307 # assert jvalue.get("a.b").to_bool
308 # ~~~
309 #
310 # Access an item in an array by its index.
311 # ~~~
312 # assert jvalue.get("b.1").to_s == "one"
313 # ~~~
314 #
315 # Any error at any depth of a query is reported. The client should usually
316 # check for errors before using the returned value.
317 # ~~~
318 # assert jvalue.get("a.b.c").to_error.to_s == "Value at `a.b` is not a map. Got a `map`"
319 # assert jvalue.get("b.3").to_error.to_s == "Index `3` out of bounds at `b`"
320 # ~~~
321 #
322 # Set `sep` to a custom string to access keys containing a dot.
323 # ~~~
324 # jvalue = """
325 # {
326 # "a.b": { "i": 123 },
327 # "c/d": [ 456 ]
328 # }""".to_json_value
329 #
330 # assert jvalue.get("a.b/i", sep="/").to_i == 123
331 # assert jvalue.get("c/d:0", sep=":").to_i == 456
332 # ~~~
333 fun get(query: Text, sep: nullable Text): JsonValue
334 do
335 if is_error then return self
336
337 sep = sep or else "."
338 var keys = query.split(sep)
339 var value = value
340 for i in [0..keys.length[ do
341 var key = keys[i]
342 if value isa MapRead[String, nullable Object] then
343 if value.has_key(key) then
344 value = value[key]
345 else
346 var sub_query = sub_query_to_s(keys, i, sep)
347 value = new JsonKeyError("Key `{key}` not found.",
348 self, sub_query)
349 break
350 end
351 else if value isa Sequence[nullable Object] then
352 if key.is_int then
353 var index = key.to_i
354 if index < value.length then
355 value = value[index]
356 else
357 var sub_query = sub_query_to_s(keys, i, sep)
358 value = new JsonKeyError("Index `{key}` out of bounds at `{sub_query}`",
359 self, sub_query)
360 break
361 end
362 end
363 else
364 var sub_query = sub_query_to_s(keys, i, sep)
365 value = new JsonKeyError("Value at `{sub_query}` is not a map. Got a `{json_type}`",
366 self, sub_query)
367 break
368 end
369 end
370 return new JsonValue(value)
371 end
372
373 # Concatenate all keys up to `last` for error reports
374 private fun sub_query_to_s(keys: Array[String], last: Int, sep: Text): String
375 do
376 return [for i in [0..last[ do keys[i]].join(sep)
377 end
378
379 # Return a human-readable description of the type.
380 #
381 # For debugging purpose only.
382 private fun json_type: String
383 do
384 if is_array then return "array"
385 if is_bool then return "bool"
386 if is_float then return "float"
387 if is_int then return "int"
388 if is_null then return "null"
389 if is_map then return "map"
390 if is_string then return "string"
391 if is_error then return "error"
392 return "undefined"
393 end
394 end
395
396 # Key access error
397 class JsonKeyError
398 super Error
399
400 # The value on which the access was requested
401 var json_value: JsonValue
402
403 # The requested key
404 #
405 # In the case of `JsonValue::get`, the sub-query that failed.
406 var key: Object
407 end