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