lib/json: split the serialization module, replace package entrypoint & update doc
[nit.git] / lib / json / serialization_read.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Services to read JSON: `from_json_string` and `JsonDeserializer`
16 module serialization_read
17
18 import ::serialization::caching
19 private import ::serialization::engine_tools
20 private import static
21 private import string_parser
22
23 # Deserializer from a Json string.
24 class JsonDeserializer
25 super CachingDeserializer
26
27 # Json text to deserialize from.
28 private var text: Text
29
30 # Root json object parsed from input text.
31 private var root: nullable Object is noinit
32
33 # Depth-first path in the serialized object tree.
34 private var path = new Array[Map[String, nullable Object]]
35
36 # Names of the attributes from the root to the object currently being deserialized
37 var attributes_path = new Array[String]
38
39 # Last encountered object reference id.
40 #
41 # See `id_to_object`.
42 var just_opened_id: nullable Int = null
43
44 init do
45 var root = text.parse_json
46 if root isa Map[String, nullable Object] then path.add(root)
47 self.root = root
48 end
49
50 redef fun deserialize_attribute(name, static_type)
51 do
52 if path.is_empty then
53 # The was a parsing error or the root is not an object
54 if not root isa Error then
55 errors.add new Error("Deserialization Error: parsed JSON value is not an object.")
56 end
57 deserialize_attribute_missing = false
58 return null
59 end
60
61 var current = path.last
62
63 if not current.keys.has(name) then
64 # Let the generated code / caller of `deserialize_attribute` raise the missing attribute error
65 deserialize_attribute_missing = true
66 return null
67 end
68
69 var value = current[name]
70
71 attributes_path.add name
72 var res = convert_object(value, static_type)
73 attributes_path.pop
74
75 deserialize_attribute_missing = false
76 return res
77 end
78
79 # This may be called multiple times by the same object from constructors
80 # in different nclassdef
81 redef fun notify_of_creation(new_object)
82 do
83 var id = just_opened_id
84 if id == null then return # Register `new_object` only once
85 cache[id] = new_object
86 end
87
88 # Convert the simple JSON `object` to a Nit object
89 private fun convert_object(object: nullable Object, static_type: nullable String): nullable Object
90 do
91 if object isa JsonParseError then
92 errors.add object
93 return null
94 end
95
96 if object isa Map[String, nullable Object] then
97 var kind = null
98 if object.keys.has("__kind") then
99 kind = object["__kind"]
100 end
101
102 # ref?
103 if kind == "ref" then
104 if not object.keys.has("__id") then
105 errors.add new Error("Serialization Error: JSON object reference does not declare a `__id`.")
106 return object
107 end
108
109 var id = object["__id"]
110 if not id isa Int then
111 errors.add new Error("Serialization Error: JSON object reference declares a non-integer `__id`.")
112 return object
113 end
114
115 if not cache.has_id(id) then
116 errors.add new Error("Serialization Error: JSON object reference has an unknown `__id`.")
117 return object
118 end
119
120 return cache.object_for(id)
121 end
122
123 # obj?
124 if kind == "obj" or kind == null then
125 var id = null
126 if object.keys.has("__id") then
127 id = object["__id"]
128
129 if not id isa Int then
130 errors.add new Error("Serialization Error: JSON object declaration declares a non-integer `__id`.")
131 return object
132 end
133
134 if cache.has_id(id) then
135 errors.add new Error("Serialization Error: JSON object with `__id` {id} is deserialized twice.")
136 # Keep going
137 end
138 end
139
140 var class_name = object.get_or_null("__class")
141 if class_name == null then
142 # Fallback to custom heuristic
143 class_name = class_name_heuristic(object)
144
145 if class_name == null and static_type != null then
146 # Fallack to the static type, strip the `nullable` prefix
147 var prefix = "nullable "
148 if static_type.has(prefix) then
149 class_name = static_type.substring_from(prefix.length)
150 else class_name = static_type
151 end
152 end
153
154 if class_name == null then
155 errors.add new Error("Serialization Error: JSON object declaration does not declare a `__class`.")
156 return object
157 end
158
159 if not class_name isa String then
160 errors.add new Error("Serialization Error: JSON object declaration declares a non-string `__class`.")
161 return object
162 end
163
164 # advance on path
165 path.push object
166
167 just_opened_id = id
168 var value = deserialize_class(class_name)
169 just_opened_id = null
170
171 # revert on path
172 path.pop
173
174 return value
175 end
176
177 # char?
178 if kind == "char" then
179 if not object.keys.has("__val") then
180 errors.add new Error("Serialization Error: JSON `char` object does not declare a `__val`.")
181 return object
182 end
183
184 var val = object["__val"]
185
186 if not val isa String or val.is_empty then
187 errors.add new Error("Serialization Error: JSON `char` object does not declare a single char in `__val`.")
188 return object
189 end
190
191 return val.chars.first
192 end
193
194 errors.add new Error("Serialization Error: JSON object has an unknown `__kind`.")
195 return object
196 end
197
198 # Simple JSON array without serialization metadata
199 if object isa Array[nullable Object] then
200 # Can we use the static type?
201 if static_type != null then
202 var prefix = "nullable "
203 var class_name = if static_type.has(prefix) then
204 static_type.substring_from(prefix.length)
205 else static_type
206
207 opened_array = object
208 var value = deserialize_class(class_name)
209 opened_array = null
210 return value
211 end
212
213 # This branch should rarely be used:
214 # when an array is the root object which is accepted but illegal in standard JSON,
215 # or in strange custom deserialization hacks.
216
217 var array = new Array[nullable Object]
218 var types = new HashSet[String]
219 var has_nullable = false
220 for e in object do
221 var res = convert_object(e)
222 array.add res
223
224 if res != null then
225 types.add res.class_name
226 else has_nullable = true
227 end
228
229 if types.length == 1 then
230 var array_type = types.first
231
232 var typed_array
233 if array_type == "ASCIIFlatString" or array_type == "UnicodeFlatString" then
234 if has_nullable then
235 typed_array = new Array[nullable FlatString]
236 else typed_array = new Array[FlatString]
237 else if array_type == "Int" then
238 if has_nullable then
239 typed_array = new Array[nullable Int]
240 else typed_array = new Array[Int]
241 else if array_type == "Float" then
242 if has_nullable then
243 typed_array = new Array[nullable Float]
244 else typed_array = new Array[Float]
245 else
246 # TODO support all array types when we separate the constructor
247 # `from_deserializer` from the filling of the items.
248
249 if not has_nullable then
250 typed_array = new Array[Object]
251 else
252 # Unsupported array type, return as `Array[nullable Object]`
253 return array
254 end
255 end
256
257 assert typed_array isa Array[nullable Object]
258
259 # Copy item to the new array
260 for e in array do typed_array.add e
261 return typed_array
262 end
263
264 # Uninferrable type, return as `Array[nullable Object]`
265 return array
266 end
267
268 return object
269 end
270
271 # Current array open for deserialization, used by `SimpleCollection::from_deserializer`
272 private var opened_array: nullable Array[nullable Object] = null
273
274 redef fun deserialize
275 do
276 errors.clear
277 return convert_object(root)
278 end
279
280 # User customizable heuristic to infer the name of the Nit class to deserialize `json_object`
281 #
282 # This method is called only when deserializing an object without the metadata `__class`.
283 # Use the content of `json_object` to identify what Nit class it should be deserialized into.
284 # Or use `self.attributes_path` indicating where the deserialized object will be stored,
285 # is is less reliable as some objects don't have an associated attribute:
286 # the root/first deserialized object and collection elements.
287 #
288 # Return the class name as a `String` when it can be inferred,
289 # or `null` when the class name cannot be found.
290 #
291 # If a valid class name is returned, `json_object` will then be deserialized normally.
292 # So it must contain the attributes of the corresponding class, as usual.
293 #
294 # ~~~
295 # class MyData
296 # serialize
297 #
298 # var data: String
299 # end
300 #
301 # class MyError
302 # serialize
303 #
304 # var error: String
305 # var related_data: MyData
306 # end
307 #
308 # class MyJsonDeserializer
309 # super JsonDeserializer
310 #
311 # redef fun class_name_heuristic(json_object)
312 # do
313 # # Infer the Nit class from the content of the JSON object.
314 # if json_object.keys.has("error") then return "MyError"
315 # if json_object.keys.has("data") then return "MyData"
316 #
317 # # Infer the Nit class from the attribute where it will be stored.
318 # # This line duplicates a previous line, and would only apply when
319 # # `MyData` is within a `MyError`.
320 # if attributes_path.not_empty and attributes_path.last == "related_data" then return "MyData"
321 #
322 # return null
323 # end
324 # end
325 #
326 # var json = """{"data": "some data"}"""
327 # var deserializer = new MyJsonDeserializer(json)
328 # var deserialized = deserializer.deserialize
329 # assert deserializer.errors.is_empty
330 # assert deserialized isa MyData
331 #
332 # json = """{"error": "some error message",
333 # "related_data": {"data": "some other data"}}"""
334 # deserializer = new MyJsonDeserializer(json)
335 # deserialized = deserializer.deserialize
336 # assert deserializer.errors.is_empty
337 # assert deserialized isa MyError
338 # ~~~
339 protected fun class_name_heuristic(json_object: Map[String, nullable Object]): nullable String
340 do
341 return null
342 end
343 end
344
345 redef class Text
346
347 # Deserialize a `nullable Object` from this JSON formatted string
348 #
349 # Warning: Deserialization errors are reported with `print_error` and
350 # may be returned as a partial object or as `null`.
351 #
352 # This method is not appropriate when errors need to be handled programmatically,
353 # manually use a `JsonDeserializer` in such cases.
354 fun from_json_string: nullable Object
355 do
356 var deserializer = new JsonDeserializer(self)
357 var res = deserializer.deserialize
358 if deserializer.errors.not_empty then
359 print_error "Deserialization Errors: {deserializer.errors.join(", ")}"
360 end
361 return res
362 end
363 end
364
365 redef class SimpleCollection[E]
366 redef init from_deserializer(v)
367 do
368 super
369 if v isa JsonDeserializer then
370 v.notify_of_creation self
371 init
372
373 var open_array: nullable SequenceRead[nullable Object] = v.opened_array
374 if open_array == null then
375 # With metadata
376 var arr = v.path.last.get_or_null("__items")
377 if not arr isa SequenceRead[nullable Object] then
378 # If there is nothing, we consider that it is an empty collection.
379 if arr != null then v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
380 return
381 end
382 open_array = arr
383 end
384
385 # Try to get the name of the single parameter type assuming it is E.
386 # This does not work in non-generic subclasses,
387 # when the first parameter is not E, or
388 # when there is more than one parameter. (The last one could be fixed)
389 var class_name = class_name
390 var items_type = null
391 var bracket_index = class_name.index_of('[')
392 if bracket_index != -1 then
393 var start = bracket_index + 1
394 var ending = class_name.last_index_of(']')
395 items_type = class_name.substring(start, ending-start)
396 end
397
398 # Fill array
399 for o in open_array do
400 var obj = v.convert_object(o, items_type)
401 if obj isa E then
402 add obj
403 else v.errors.add new AttributeTypeError(self, "items", obj, "E")
404 end
405 end
406 end
407 end
408
409 redef class Map[K, V]
410 redef init from_deserializer(v)
411 do
412 super
413
414 if v isa JsonDeserializer then
415 v.notify_of_creation self
416 init
417
418 var length = v.deserialize_attribute("__length")
419 var keys = v.path.last.get_or_null("__keys")
420 var values = v.path.last.get_or_null("__values")
421
422 # Length is optional
423 if length == null and keys isa SequenceRead[nullable Object] then length = keys.length
424
425 # Consistency check
426 if not length isa Int or length < 0 or
427 not keys isa SequenceRead[nullable Object] or
428 not values isa SequenceRead[nullable Object] or
429 keys.length != values.length or length != keys.length then
430
431 # If there is nothing or length == 0, we consider that it is an empty Map.
432 if (length != null and length != 0) or keys != null or values != null then
433 v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
434 end
435 return
436 end
437
438 for i in length.times do
439 var key = v.convert_object(keys[i])
440 var value = v.convert_object(values[i])
441
442 if not key isa K then
443 v.errors.add new AttributeTypeError(self, "keys", key, "K")
444 continue
445 end
446
447 if not value isa V then
448 v.errors.add new AttributeTypeError(self, "values", value, "V")
449 continue
450 end
451
452 if has_key(key) then
453 v.errors.add new Error("Deserialization Error: duplicated key '{key or else "null"}' in {self.class_name}, previous value overwritten")
454 end
455
456 self[key] = value
457 end
458 end
459 end
460 end