lib: intro the friendlier to use json::dynamic
[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 private import static
24 import standard
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 HashMap[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 HashMap[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 Array[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 Array[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 # Iterator over the values of the array `self`
166 #
167 # require: `self.is_array`
168 #
169 # var a = new Array[String]
170 # for e in """["a", "b", "c"]""".to_json_value do a.add(e.to_s)
171 # assert a[0] == "a"
172 # assert a[1] == "b"
173 # assert a[2] == "c"
174 fun iterator: Iterator[JsonValue] do return to_a.iterator
175
176 # Get value at index `key` on the array or map `self`
177 #
178 # require: `self.is_array or self.is_map`
179 # require: `self.is_array implies key isa Int`
180 #
181 # assert """{"a": 123}""".to_json_value["a"].to_i == 123
182 # assert """{"123": "a"}""".to_json_value[123].to_s == "a"
183 # assert """{"John Smith": 1980}""".to_json_value[["John ", "Smith"]].to_i == 1980
184 #
185 # assert """["a", "b", "c"]""".to_json_value[0].to_s == "a"
186 fun [](key: Object): JsonValue
187 do
188 var value = value
189 if value isa HashMap[String, nullable Object] then
190 return new JsonValue(value[key.to_s])
191 else if value isa Array[nullable Object] then
192 assert key isa Int
193 return new JsonValue(value[key])
194 else abort
195 end
196
197 # Advanced query to get a value within the map `self` or it's children.
198 #
199 # A query is composed of the keys to each map seperated by '.'.
200 #
201 # require: `self.is_map`
202 #
203 # assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a").is_map
204 # assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t").to_bool
205 # assert not """{"a": {"t": true, "f": false}}""".to_json_value.get("a.f").to_bool
206 # assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.b.c.d").to_i == 123
207 fun get(query: String): JsonValue
208 do
209 var keys = query.split(".")
210 var value = value
211 for key in keys do
212 assert value isa HashMap[String, nullable Object]
213 value = value[key]
214 end
215 return new JsonValue(value)
216 end
217 end
218
219 redef class String
220 # Parse `self` to obtain a `JsonValue`
221 fun to_json_value: JsonValue
222 do
223 var value = json_to_nit_object
224 return new JsonValue(value)
225 end
226 end