3c8b170227c366f13c86bdf614a7b966e85bb270
[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 import poset
23
24 # Deserializer from a Json string.
25 class JsonDeserializer
26 super CachingDeserializer
27
28 # Json text to deserialize from.
29 private var text: Text
30
31 # Accepted parameterized classes to deserialize
32 #
33 # If `whitelist.empty`, all types are accepted.
34 #
35 # ~~~nitish
36 # import json::serialization
37 #
38 # class MyClass
39 # serialize
40 # end
41 #
42 # var json_string = """
43 # {"__class" = "MyClass"}
44 # """
45 #
46 # var deserializer = new JsonDeserializer(json_string)
47 # var obj = deserializer.deserialize
48 # assert deserializer.errors.is_empty
49 # assert obj isa MyClass
50 #
51 # deserializer = new JsonDeserializer(json_string)
52 # deserializer.whitelist.add "Array[String]"
53 # deserializer.whitelist.add "AnotherAcceptedClass"
54 # obj = deserializer.deserialize
55 # assert deserializer.errors.length == 1
56 # assert obj == null
57 # ~~~
58 var whitelist = new Array[Text]
59
60 # Should objects be checked if they a subtype of the static type before deserialization?
61 #
62 # Defaults to `true`, as it should always be activated.
63 # It can be turned off to implement the subtype check itself.
64 var check_subtypes = true is writable
65
66 # Root json object parsed from input text.
67 private var root: nullable Object is noinit
68
69 # Depth-first path in the serialized object tree.
70 private var path = new Array[Map[String, nullable Object]]
71
72 # Names of the attributes from the root to the object currently being deserialized
73 var attributes_path = new Array[String]
74
75 # Last encountered object reference id.
76 #
77 # See `id_to_object`.
78 var just_opened_id: nullable Int = null
79
80 init do
81 var root = text.parse_json
82 if root isa Map[String, nullable Object] then path.add(root)
83 self.root = root
84 end
85
86 redef fun deserialize_attribute(name, static_type)
87 do
88 if path.is_empty then
89 # The was a parsing error or the root is not an object
90 if not root isa Error then
91 errors.add new Error("Deserialization Error: parsed JSON value is not an object.")
92 end
93 deserialize_attribute_missing = false
94 return null
95 end
96
97 var current = path.last
98
99 if not current.keys.has(name) then
100 # Let the generated code / caller of `deserialize_attribute` raise the missing attribute error
101 deserialize_attribute_missing = true
102 return null
103 end
104
105 var value = current[name]
106
107 attributes_path.add name
108 var res = convert_object(value, static_type)
109 attributes_path.pop
110
111 deserialize_attribute_missing = false
112 return res
113 end
114
115 # This may be called multiple times by the same object from constructors
116 # in different nclassdef
117 redef fun notify_of_creation(new_object)
118 do
119 var id = just_opened_id
120 if id == null then return # Register `new_object` only once
121 cache[id] = new_object
122 end
123
124 # Convert the simple JSON `object` to a Nit object
125 private fun convert_object(object: nullable Object, static_type: nullable String): nullable Object
126 do
127 if object isa JsonParseError then
128 errors.add object
129 return null
130 end
131
132 if object isa Map[String, nullable Object] then
133 var kind = null
134 if object.keys.has("__kind") then
135 kind = object["__kind"]
136 end
137
138 # ref?
139 if kind == "ref" then
140 if not object.keys.has("__id") then
141 errors.add new Error("Deserialization Error: JSON object reference does not declare a `__id`.")
142 return object
143 end
144
145 var id = object["__id"]
146 if not id isa Int then
147 errors.add new Error("Deserialization Error: JSON object reference declares a non-integer `__id`.")
148 return object
149 end
150
151 if not cache.has_id(id) then
152 errors.add new Error("Deserialization Error: JSON object reference has an unknown `__id`.")
153 return object
154 end
155
156 return cache.object_for(id)
157 end
158
159 # obj?
160 if kind == "obj" or kind == null then
161 var id = null
162 if object.keys.has("__id") then
163 id = object["__id"]
164
165 if not id isa Int then
166 errors.add new Error("Deserialization Error: JSON object declaration declares a non-integer `__id`.")
167 return object
168 end
169
170 if cache.has_id(id) then
171 errors.add new Error("Deserialization Error: JSON object with `__id` {id} is deserialized twice.")
172 # Keep going
173 end
174 end
175
176 var class_name = object.get_or_null("__class")
177 if class_name == null then
178 # Fallback to custom heuristic
179 class_name = class_name_heuristic(object)
180
181 if class_name == null and static_type != null then
182 # Fallack to the static type, strip the `nullable` prefix
183 var prefix = "nullable "
184 if static_type.has_prefix(prefix) then
185 class_name = static_type.substring_from(prefix.length)
186 else class_name = static_type
187 end
188 end
189
190 if class_name == null then
191 errors.add new Error("Deserialization Error: JSON object declaration does not declare a `__class`.")
192 return object
193 end
194
195 if not class_name isa String then
196 errors.add new Error("Deserialization Error: JSON object declaration declares a non-string `__class`.")
197 return object
198 end
199
200 if whitelist.not_empty and not whitelist.has(class_name) then
201 errors.add new Error("Deserialization Error: '{class_name}' not in whitelist")
202 return null
203 end
204
205 if static_type != null and check_subtypes then
206 var static_class = static_type.strip_nullable_and_params
207 var dynamic_class = class_name.strip_nullable_and_params
208 if not class_inheritance_metamodel.has_edge(dynamic_class, static_class) then
209 errors.add new Error("Deserialization Error: `{class_name}` is not a subtype of the static type `{static_type}`")
210 return null
211 end
212 end
213
214 # advance on path
215 path.push object
216
217 just_opened_id = id
218 var value = deserialize_class(class_name)
219 just_opened_id = null
220
221 # revert on path
222 path.pop
223
224 return value
225 end
226
227 # char?
228 if kind == "char" then
229 if not object.keys.has("__val") then
230 errors.add new Error("Deserialization Error: JSON `char` object does not declare a `__val`.")
231 return object
232 end
233
234 var val = object["__val"]
235
236 if not val isa String or val.is_empty then
237 errors.add new Error("Deserialization Error: JSON `char` object does not declare a single char in `__val`.")
238 return object
239 end
240
241 return val.chars.first
242 end
243
244 # byte?
245 if kind == "byte" then
246 var val = object.get_or_null("__val")
247 if not val isa Int then
248 errors.add new Error("Serialization Error: JSON `byte` object does not declare an integer `__val`.")
249 return object
250 end
251
252 return val.to_b
253 end
254
255 errors.add new Error("Deserialization Error: JSON object has an unknown `__kind`.")
256 return object
257 end
258
259 # Simple JSON array without serialization metadata
260 if object isa Array[nullable Object] then
261 # Can we use the static type?
262 if static_type != null then
263 var prefix = "nullable "
264 var class_name = if static_type.has(prefix) then
265 static_type.substring_from(prefix.length)
266 else static_type
267
268 opened_array = object
269 var value = deserialize_class(class_name)
270 opened_array = null
271 return value
272 end
273
274 # This branch should rarely be used:
275 # when an array is the root object which is accepted but illegal in standard JSON,
276 # or in strange custom deserialization hacks.
277
278 var array = new Array[nullable Object]
279 var types = new HashSet[String]
280 var has_nullable = false
281 for e in object do
282 var res = convert_object(e)
283 array.add res
284
285 if res != null then
286 types.add res.class_name
287 else has_nullable = true
288 end
289
290 if types.length == 1 then
291 var array_type = types.first
292
293 var typed_array
294 if array_type == "ASCIIFlatString" or array_type == "UnicodeFlatString" then
295 if has_nullable then
296 typed_array = new Array[nullable FlatString]
297 else typed_array = new Array[FlatString]
298 else if array_type == "Int" then
299 if has_nullable then
300 typed_array = new Array[nullable Int]
301 else typed_array = new Array[Int]
302 else if array_type == "Float" then
303 if has_nullable then
304 typed_array = new Array[nullable Float]
305 else typed_array = new Array[Float]
306 else
307 # TODO support all array types when we separate the constructor
308 # `from_deserializer` from the filling of the items.
309
310 if not has_nullable then
311 typed_array = new Array[Object]
312 else
313 # Unsupported array type, return as `Array[nullable Object]`
314 return array
315 end
316 end
317
318 assert typed_array isa Array[nullable Object]
319
320 # Copy item to the new array
321 for e in array do typed_array.add e
322 return typed_array
323 end
324
325 # Uninferrable type, return as `Array[nullable Object]`
326 return array
327 end
328
329 return object
330 end
331
332 # Current array open for deserialization, used by `SimpleCollection::from_deserializer`
333 private var opened_array: nullable Array[nullable Object] = null
334
335 redef fun deserialize(static_type)
336 do
337 errors.clear
338 return convert_object(root, static_type)
339 end
340
341 # User customizable heuristic to infer the name of the Nit class to deserialize `json_object`
342 #
343 # This method is called only when deserializing an object without the metadata `__class`.
344 # Use the content of `json_object` to identify what Nit class it should be deserialized into.
345 # Or use `self.attributes_path` indicating where the deserialized object will be stored,
346 # is is less reliable as some objects don't have an associated attribute:
347 # the root/first deserialized object and collection elements.
348 #
349 # Return the class name as a `String` when it can be inferred,
350 # or `null` when the class name cannot be found.
351 #
352 # If a valid class name is returned, `json_object` will then be deserialized normally.
353 # So it must contain the attributes of the corresponding class, as usual.
354 #
355 # ~~~
356 # class MyData
357 # serialize
358 #
359 # var data: String
360 # end
361 #
362 # class MyError
363 # serialize
364 #
365 # var error: String
366 # var related_data: MyData
367 # end
368 #
369 # class MyJsonDeserializer
370 # super JsonDeserializer
371 #
372 # redef fun class_name_heuristic(json_object)
373 # do
374 # # Infer the Nit class from the content of the JSON object.
375 # if json_object.keys.has("error") then return "MyError"
376 # if json_object.keys.has("data") then return "MyData"
377 #
378 # # Infer the Nit class from the attribute where it will be stored.
379 # # This line duplicates a previous line, and would only apply when
380 # # `MyData` is within a `MyError`.
381 # if attributes_path.not_empty and attributes_path.last == "related_data" then return "MyData"
382 #
383 # return null
384 # end
385 # end
386 #
387 # var json = """{"data": "some data"}"""
388 # var deserializer = new MyJsonDeserializer(json)
389 # var deserialized = deserializer.deserialize
390 # assert deserializer.errors.is_empty
391 # assert deserialized isa MyData
392 #
393 # json = """{"error": "some error message",
394 # "related_data": {"data": "some other data"}}"""
395 # deserializer = new MyJsonDeserializer(json)
396 # deserialized = deserializer.deserialize
397 # assert deserializer.errors.is_empty
398 # assert deserialized isa MyError
399 # ~~~
400 protected fun class_name_heuristic(json_object: Map[String, nullable Object]): nullable String
401 do
402 return null
403 end
404 end
405
406 redef class Text
407
408 # Deserialize a `nullable Object` from this JSON formatted string
409 #
410 # Warning: Deserialization errors are reported with `print_error` and
411 # may be returned as a partial object or as `null`.
412 #
413 # This method is not appropriate when errors need to be handled programmatically,
414 # manually use a `JsonDeserializer` in such cases.
415 fun from_json_string: nullable Object
416 do
417 var deserializer = new JsonDeserializer(self)
418 var res = deserializer.deserialize
419 if deserializer.errors.not_empty then
420 print_error "Deserialization Errors: {deserializer.errors.join(", ")}"
421 end
422 return res
423 end
424
425 # Strip the `nullable` prefix and the params from the class name `self`
426 #
427 # ~~~nitish
428 # assert "String".strip_nullable_and_params == "String"
429 # assert "Array[Int]".strip_nullable_and_params == "Array"
430 # assert "Map[Set[String], Set[Int]]".strip_nullable_and_params == "Map"
431 # ~~~
432 private fun strip_nullable_and_params: String
433 do
434 var class_name = to_s
435
436 var prefix = "nullable "
437 if class_name.has_prefix(prefix) then class_name = class_name.substring_from(prefix.length)
438
439 var bracket_index = class_name.index_of('[')
440 if bracket_index == -1 then return class_name
441 return class_name.substring(0, bracket_index)
442 end
443 end
444
445 redef class SimpleCollection[E]
446 redef init from_deserializer(v)
447 do
448 super
449 if v isa JsonDeserializer then
450 v.notify_of_creation self
451 init
452
453 var open_array: nullable SequenceRead[nullable Object] = v.opened_array
454 if open_array == null then
455 # With metadata
456 var arr = v.path.last.get_or_null("__items")
457 if not arr isa SequenceRead[nullable Object] then
458 # If there is nothing, we consider that it is an empty collection.
459 if arr != null then v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
460 return
461 end
462 open_array = arr
463 end
464
465 # Name of the dynamic name of E
466 var items_type_name = (new GetName[E]).to_s
467
468 # Fill array
469 for o in open_array do
470 var obj = v.convert_object(o, items_type_name)
471 if obj isa E then
472 add obj
473 else v.errors.add new AttributeTypeError(self, "items", obj, items_type_name)
474 end
475 end
476 end
477 end
478
479 redef class Map[K, V]
480 redef init from_deserializer(v)
481 do
482 super
483
484 if v isa JsonDeserializer then
485 v.notify_of_creation self
486 init
487
488 var keys_type_name = (new GetName[K]).to_s
489 var values_type_name = (new GetName[V]).to_s
490
491 var length = v.deserialize_attribute("__length")
492 var keys = v.path.last.get_or_null("__keys")
493 var values = v.path.last.get_or_null("__values")
494
495 if keys == null and values == null then
496 # Fallback to a plain object
497 for key, value_src in v.path.last do
498
499 var value = v.convert_object(value_src, values_type_name)
500
501 if not key isa K then
502 v.errors.add new AttributeTypeError(self, "keys", key, keys_type_name)
503 continue
504 end
505
506 if not value isa V then
507 v.errors.add new AttributeTypeError(self, "values", value, values_type_name)
508 continue
509 end
510
511 self[key] = value
512 end
513 return
514 end
515
516 # Length is optional
517 if length == null and keys isa SequenceRead[nullable Object] then length = keys.length
518
519 # Consistency check
520 if not length isa Int or length < 0 or
521 not keys isa SequenceRead[nullable Object] or
522 not values isa SequenceRead[nullable Object] or
523 keys.length != values.length or length != keys.length then
524
525 # If there is nothing or length == 0, we consider that it is an empty Map.
526 if (length != null and length != 0) or keys != null or values != null then
527 v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
528 end
529 return
530 end
531
532 # First, convert all keys to follow the order of the serialization
533 var converted_keys = new Array[K]
534 for i in length.times do
535 var key = v.convert_object(keys[i], keys_type_name)
536
537 if not key isa K then
538 v.errors.add new AttributeTypeError(self, "keys", key, keys_type_name)
539 continue
540 end
541
542 converted_keys.add key
543 end
544
545 # Then convert the values and build the map
546 for i in length.times do
547 var key = converted_keys[i]
548 var value = v.convert_object(values[i], values_type_name)
549
550 if not value isa V then
551 v.errors.add new AttributeTypeError(self, "values", value, values_type_name)
552 continue
553 end
554
555 if has_key(key) then
556 v.errors.add new Error("Deserialization Error: duplicated key '{key or else "null"}' in {self.class_name}, previous value overwritten")
557 end
558
559 self[key] = value
560 end
561 end
562 end
563 end
564
565 # ---
566 # Metamodel
567
568 # Class inheritance graph as a `POSet[String]` serialized to JSON
569 private fun class_inheritance_metamodel_json: CString is intern
570
571 redef class Sys
572 # Class inheritance graph
573 #
574 # ~~~
575 # var hierarchy = class_inheritance_metamodel
576 # assert hierarchy.has_edge("String", "Object")
577 # assert not hierarchy.has_edge("Object", "String")
578 # ~~~
579 var class_inheritance_metamodel: POSet[String] is lazy do
580 var engine = new JsonDeserializer(class_inheritance_metamodel_json.to_s)
581 engine.check_subtypes = false
582 engine.whitelist.add_all(
583 ["String", "POSet[String]", "POSetElement[String]", "HashSet[String]", "HashMap[String, POSetElement[String]]"])
584 var poset = engine.deserialize
585 if engine.errors.not_empty then
586 print_error engine.errors.join("\n")
587 return new POSet[String]
588 end
589 if poset isa POSet[String] then return poset
590 return new POSet[String]
591 end
592 end
593
594 redef class Deserializer
595 redef fun deserialize_class(name)
596 do
597 if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
598 if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
599 if name == "HashSet[String]" then return new HashSet[String].from_deserializer(self)
600 if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
601
602 return super
603 end
604 end