1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # Dynamic interface to read values from JSON strings
19 # Most services are in `JsonValue`, which is created by `Text::to_json_value`.
26 # Parse `self` to a `JsonValue`
27 fun to_json_value
: JsonValue do return new JsonValue(parse_json
)
30 # Dynamic wrapper of a JSON value, created by `Text::to_json_value`
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.
45 # "a": ["zero", "one", "two"]
47 # var json_value = json_src.to_json_value # Parse src to a `JsonValue`
50 # Access array or map values using their indices.
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
57 # Use `get` to reach a value nested in multiple objects.
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
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.
69 # The wrapped JSON value.
70 var value
: nullable Object
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
78 # Is this value an integer?
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
85 # Get this value as a `Int`
87 # require: `self.is_numeric`
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
95 assert value
isa Numeric
99 # Is this value a float?
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
106 # Get this value as a `Float`
108 # require: `self.is_numeric`
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
116 assert value
isa Numeric
120 # Is the value numeric?
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
128 # Get this value as a `Numeric`
130 # require: `self.is_numeric`
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
136 if is_int
then return to_i
140 # Is this value a boolean?
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
146 # Get this value as a `Bool`
148 # require: `self.is_bool`
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)
154 # Is this value a string?
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
160 # Get this value as a `String`
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`.
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
173 # Is this value a Json object (a map)?
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]
179 # Get this value as a `Map[String, JsonValue]`
181 # require: `self.is_map`
182 fun to_map
: Map[String, JsonValue] do
184 assert value
isa MapRead[String, nullable Object]
186 var map
= new HashMap[String, JsonValue]
187 for k
, v
in value
do map
[k
] = new JsonValue(v
)
193 # Is this value an array?
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]
201 # Get this value as an `Array[JsonValue]`
203 # require: `self.is_array`
205 # assert """["a", "b", "c"]""".to_json_value.to_a.join(", ") == "a, b, c"
206 fun to_a
: Array[JsonValue]
209 assert value
isa SequenceRead[nullable Object]
211 var a
= new Array[JsonValue]
212 for e
in value
do a
.add
(new JsonValue(e
))
218 # Is this value an error?
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
225 # Get this value as a `Error`.
227 # require: `self.is_error`
228 fun to_error
: Error do return value
.as(Error)
232 # Iterator over the values of the array `self`
234 # require: `self.is_array`
236 # var a = new Array[String]
237 # for e in """["a", "b", "c"]""".to_json_value do a.add(e.to_s)
241 fun iterator
: Iterator[JsonValue] do return to_a
.iterator
243 # Get value at index `key` on the array or map `self`
245 # require: `self.is_array or self.is_map`
246 # require: `self.is_array implies key isa Int`
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
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
257 var result
: nullable Object
260 else if value
isa MapRead[String, nullable Object] then
262 if value
.has_key
(key
) then
265 result
= new JsonKeyError("Key `{key}` not found.", self, key
)
267 else if value
isa SequenceRead[nullable Object] then
269 if key
< value
.length
and key
>= 0 then
272 result
= new JsonKeyError("Index `{key}` out of bounds.",
276 result
= new JsonKeyError("Invalid key type. Expecting `Int`. Got `{key.class_name}`.",
280 result
= new JsonKeyError("Invalid `[]` access on a `{json_type}` JsonValue.",
283 return new JsonValue(result
)
286 # Get the value at `query`, a string of map keys and array indices
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.
291 # Given the following JSON object parsed as a `JsonValue`.
299 # "b": ["zero", "one", "two"]
303 # Access a value in maps by its key, starting from the key in the root object.
305 # assert jvalue.get("a").is_map
306 # assert jvalue.get("a.i").to_i == 123
307 # assert jvalue.get("a.b").to_bool
310 # Access an item in an array by its index.
312 # assert jvalue.get("b.1").to_s == "one"
315 # Any error at any depth of a query is reported. The client should usually
316 # check for errors before using the returned value.
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`"
322 # Set `sep` to a custom string to access keys containing a dot.
326 # "a.b": { "i": 123 },
330 # assert jvalue.get("a.b/i", sep="/").to_i == 123
331 # assert jvalue.get("c/d:0", sep=":").to_i == 456
333 fun get
(query
: Text, sep
: nullable Text): JsonValue
335 if is_error
then return self
337 sep
= sep
or else "."
338 var keys
= query
.split
(sep
)
340 for i
in [0..keys
.length
[ do
342 if value
isa MapRead[String, nullable Object] then
343 if value
.has_key
(key
) then
346 var sub_query
= sub_query_to_s
(keys
, i
, sep
)
347 value
= new JsonKeyError("Key `{key}` not found.",
351 else if value
isa Sequence[nullable Object] then
354 if index
< value
.length
then
357 var sub_query
= sub_query_to_s
(keys
, i
, sep
)
358 value
= new JsonKeyError("Index `{key}` out of bounds at `{sub_query}`",
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}`",
370 return new JsonValue(value
)
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
376 return [for i
in [0..last
[ do keys
[i
]].join
(sep
)
379 # Return a human-readable description of the type.
381 # For debugging purpose only.
382 private fun json_type
: String
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"
400 # The value on which the access was requested
401 var json_value
: JsonValue
405 # In the case of `JsonValue::get`, the sub-query that failed.