json::dynamic: fix warning
[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 JSON strings.
18 #
19 # `String::to_json_value` returns a `JsonValue` which can be queried
20 # to get the underlying JSON data.
21 module dynamic
22
23 import error
24 private import static
25
26 # Wraps a JSON value.
27 #
28 # Offer methods to query the type, to dynamicaly cast the underlying value and
29 # to query elements (in case of a JSON object or a JSON array).
30 #
31 # Use `String::to_json_value` to get a `JsonValue` from a string.
32 class JsonValue
33
34 # The wrapped JSON value.
35 var value: nullable Object
36
37 # Is this value null?
38 #
39 # assert "null".to_json_value.is_null
40 # assert not "123".to_json_value.is_null
41 fun is_null: Bool do return value == null
42
43 # Is this value an integer?
44 #
45 # assert "123".to_json_value.is_int
46 # assert not "1.23".to_json_value.is_int
47 # assert not "\"str\"".to_json_value.is_int
48 fun is_int: Bool do return value isa Int
49
50 # Get this value as a `Int`
51 #
52 # require: `self.is_numeric`
53 #
54 # assert "-10".to_json_value.to_i == -10
55 # assert "123".to_json_value.to_i == 123
56 # assert "123.456".to_json_value.to_i == 123
57 fun to_i: Int
58 do
59 var value = value
60 assert value isa Numeric
61 return value.to_i
62 end
63
64 # Is this value a float?
65 #
66 # assert "0.0".to_json_value.is_float
67 # assert "123.456".to_json_value.is_float
68 # assert not "123".to_json_value.is_float
69 fun is_float: Bool do return value isa Float
70
71 # Get this value as a `Float`
72 #
73 # require: `self.is_numeric`
74 #
75 # assert "0.0".to_json_value.to_f == 0.0
76 # assert "123.456".to_json_value.to_f == 123.456
77 # assert "123".to_json_value.to_f == 123.0
78 fun to_f: Float
79 do
80 var value = value
81 assert value isa Numeric
82 return value.to_f
83 end
84
85 # Is the value numeric?
86 #
87 # assert "1.234".to_json_value.is_numeric
88 # assert "1234".to_json_value.is_numeric
89 # assert not "\"str\"".to_json_value.is_numeric
90 # assert not "1.2.3.4".to_json_value.is_numeric
91 fun is_numeric: Bool do return is_int or is_float
92
93 # Get this value as a `Numeric`
94 #
95 # require: `self.is_numeric`
96 #
97 # assert "1.234".to_json_value.to_numeric == 1.234
98 # assert "1234".to_json_value.to_numeric == 1234
99 fun to_numeric: Numeric
100 do
101 if is_int then return to_i
102 return to_f
103 end
104
105 # Is this value a boolean?
106 #
107 # assert "true".to_json_value.is_bool
108 # assert "false".to_json_value.is_bool
109 fun is_bool: Bool do return value isa Bool
110
111 # Get this value as a `Bool`
112 #
113 # require: `self.is_bool`
114 #
115 # assert "true".to_json_value.to_bool
116 # assert not "false".to_json_value.to_bool
117 fun to_bool: Bool do return value.as(Bool)
118
119 # Is this value a string?
120 #
121 # assert "\"str\"".to_json_value.is_string
122 # assert not "123".to_json_value.is_string
123 fun is_string: Bool do return value isa String
124
125 # Get this value as a `String`
126 #
127 # If value is null, return "null", otherwise returns `value.to_s`. It is practical
128 # on most types, except maps which does not have a custom `to_s`.
129 #
130 # assert "\"str\"".to_json_value.to_s == "str"
131 # assert "123".to_json_value.to_s == "123"
132 # assert "true".to_json_value.to_s == "true"
133 # assert "[1, 2, 3]".to_json_value.to_s == "[1,2,3]"
134 redef fun to_s do return (value or else "null").to_s
135
136 ### Objects
137
138 # Is this value a Json object (a map)?
139 #
140 # assert """{"a": 123}""".to_json_value.is_map
141 # assert not "123".to_json_value.is_map
142 fun is_map: Bool do return value isa MapRead[String, nullable Object]
143
144 # Get this value as a `Map[String, JsonValue]`
145 #
146 # require: `self.is_map`
147 fun to_map: Map[String, JsonValue] do
148 var value = value
149 assert value isa MapRead[String, nullable Object]
150
151 var map = new HashMap[String, JsonValue]
152 for k, v in value do map[k] = new JsonValue(v)
153 return map
154 end
155
156 ### Arrays
157
158 # Is this value an array?
159 #
160 # assert "[]".to_json_value.is_array
161 # assert "[1, 2, 3, 4, 5]".to_json_value.is_array
162 # assert "[null, true, false, 0.0, 1, \"str\"]".to_json_value.is_array
163 # assert """["a", "b", "c"]""".to_json_value.is_array
164 fun is_array: Bool do return value isa SequenceRead[nullable Object]
165
166 # Get this value as an `Array[JsonValue]`
167 #
168 # require: `self.is_array`
169 #
170 # assert """["a", "b", "c"]""".to_json_value.to_a.join(", ") == "a, b, c"
171 fun to_a: Array[JsonValue]
172 do
173 var value = value
174 assert value isa SequenceRead[nullable Object]
175
176 var a = new Array[JsonValue]
177 for e in value do a.add(new JsonValue(e))
178 return a
179 end
180
181 ### Error
182
183 # Is this value an error?
184 #
185 # assert "[]".to_json_value[0].is_error
186 # assert "[".to_json_value.is_error
187 # assert not "[]".to_json_value.is_error
188 fun is_error: Bool do return value isa Error
189
190 # Get this value as a `Error`.
191 #
192 # require: `self.is_error`
193 fun to_error: Error do return value.as(Error)
194
195 ### JsonParseError
196
197 # Is this value a parse error?
198 #
199 # assert "[".to_json_value.is_parse_error
200 # assert not "[]".to_json_value.is_parse_error
201 fun is_parse_error: Bool do return value isa JsonParseError
202
203 # Get this value as a `JsonParseError`.
204 #
205 # require: `self.is_parse_error`
206 fun to_parse_error: JsonParseError do return value.as(JsonParseError)
207
208 ### Children access
209
210 # Iterator over the values of the array `self`
211 #
212 # require: `self.is_array`
213 #
214 # var a = new Array[String]
215 # for e in """["a", "b", "c"]""".to_json_value do a.add(e.to_s)
216 # assert a[0] == "a"
217 # assert a[1] == "b"
218 # assert a[2] == "c"
219 fun iterator: Iterator[JsonValue] do return to_a.iterator
220
221 # Get value at index `key` on the array or map `self`
222 #
223 # require: `self.is_array or self.is_map`
224 # require: `self.is_array implies key isa Int`
225 #
226 # assert """{"a": 123}""".to_json_value["a"].to_i == 123
227 # assert """{"123": "a"}""".to_json_value[123].to_s == "a"
228 # assert """{"John Smith": 1980}""".to_json_value["John Smith"].to_i == 1980
229 # assert """{"a": 123}""".to_json_value["b"].is_error
230 #
231 # assert """["a", "b", "c"]""".to_json_value[0].to_s == "a"
232 # assert """["a", "b", "c"]""".to_json_value[3].is_error
233 fun [](key: Object): JsonValue do
234 var value = value
235 var result: nullable Object
236 if is_error then
237 return self
238 else if value isa MapRead[String, nullable Object] then
239 key = key.to_s
240 if value.has_key(key) then
241 result = value[key]
242 else
243 result = new JsonKeyError("Key `{key}` not found.", self, key)
244 end
245 else if value isa SequenceRead[nullable Object] then
246 if key isa Int then
247 if key < value.length and key >= 0 then
248 result = value[key]
249 else
250 result = new JsonKeyError("Index `{key}` out of bounds.",
251 self, key)
252 end
253 else
254 result = new JsonKeyError("Invalid key type. Expecting `Int`. Got `{key.class_name}`.",
255 self, key)
256 end
257 else
258 result = new JsonKeyError("Invalid `[]` access on a `{json_type}` JsonValue.",
259 self, key)
260 end
261 return new JsonValue(result)
262 end
263
264 # Advanced query to get a value within the map `self` or it's children.
265 #
266 # A query is composed of the keys to each map seperated by '.'.
267 #
268 # require: `self.is_map`
269 #
270 # assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a").is_map
271 # assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t").to_bool
272 # assert not """{"a": {"t": true, "f": false}}""".to_json_value.get("a.f").to_bool
273 # assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t.t").is_error
274 # assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.b.c.d").to_i == 123
275 # assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.z.c.d").is_error
276 fun get(query: String): JsonValue do
277 var keys = query.split(".")
278 var value = value
279 if is_error then return self
280 for i in [0..keys.length[ do
281 var key = keys[i]
282 if value isa MapRead[String, nullable Object] then
283 if value.has_key(key) then
284 value = value[key]
285 else
286 var sub_query = sub_query_to_s(keys, i)
287 var e = new JsonKeyError("Key `{key}` not found.",
288 self, sub_query)
289 return new JsonValue(e)
290 end
291 else
292 var sub_query = sub_query_to_s(keys, i)
293 var val_type = (new JsonValue(value)).json_type
294 var e = new JsonKeyError("Value at `{sub_query}` is not a map. Got type `{val_type}`",
295 self, sub_query)
296 return new JsonValue(e)
297 end
298 end
299 return new JsonValue(value)
300 end
301
302 # Concatenate all keys up to `last` for debugging purposes.
303 #
304 # Note: This method deletes elements in `keys`.
305 private fun sub_query_to_s(keys: Array[String], last: Int): String do
306 last += 1
307 for j in [last..keys.length[ do keys.pop
308 return keys.join(".")
309 end
310
311 # Return a human-readable description of the type.
312 #
313 # For debugging purpose only.
314 fun json_type: String do
315 if is_array then return "array"
316 if is_bool then return "bool"
317 if is_float then return "float"
318 if is_int then return "int"
319 if is_null then return "null"
320 if is_map then return "map"
321 if is_string then return "string"
322 if is_parse_error then return "parse_error"
323 if is_error then return "error"
324 return "undefined"
325 end
326 end
327
328 # Keyed access failed.
329 class JsonKeyError
330 super Error
331
332 # The value on which the access was requested.
333 var json_value: JsonValue
334
335 # The requested key.
336 #
337 # In the case of `JsonValue.get`, the sub-query that failed.
338 var key: Object
339 end
340
341 redef class Text
342 # Parse `self` to obtain a `JsonValue`
343 fun to_json_value: JsonValue do
344 var value = parse_json
345 return new JsonValue(value)
346 end
347 end