91bd9144852d64c3cec298f831a6329408a16977
[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("Serialization 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("Serialization 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("Serialization 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("Serialization 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("Serialization 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("Serialization 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("Serialization 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("Serialization 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("Serialization 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 errors.add new Error("Serialization Error: JSON object has an unknown `__kind`.")
245 return object
246 end
247
248 # Simple JSON array without serialization metadata
249 if object isa Array[nullable Object] then
250 # Can we use the static type?
251 if static_type != null then
252 var prefix = "nullable "
253 var class_name = if static_type.has(prefix) then
254 static_type.substring_from(prefix.length)
255 else static_type
256
257 opened_array = object
258 var value = deserialize_class(class_name)
259 opened_array = null
260 return value
261 end
262
263 # This branch should rarely be used:
264 # when an array is the root object which is accepted but illegal in standard JSON,
265 # or in strange custom deserialization hacks.
266
267 var array = new Array[nullable Object]
268 var types = new HashSet[String]
269 var has_nullable = false
270 for e in object do
271 var res = convert_object(e)
272 array.add res
273
274 if res != null then
275 types.add res.class_name
276 else has_nullable = true
277 end
278
279 if types.length == 1 then
280 var array_type = types.first
281
282 var typed_array
283 if array_type == "ASCIIFlatString" or array_type == "UnicodeFlatString" then
284 if has_nullable then
285 typed_array = new Array[nullable FlatString]
286 else typed_array = new Array[FlatString]
287 else if array_type == "Int" then
288 if has_nullable then
289 typed_array = new Array[nullable Int]
290 else typed_array = new Array[Int]
291 else if array_type == "Float" then
292 if has_nullable then
293 typed_array = new Array[nullable Float]
294 else typed_array = new Array[Float]
295 else
296 # TODO support all array types when we separate the constructor
297 # `from_deserializer` from the filling of the items.
298
299 if not has_nullable then
300 typed_array = new Array[Object]
301 else
302 # Unsupported array type, return as `Array[nullable Object]`
303 return array
304 end
305 end
306
307 assert typed_array isa Array[nullable Object]
308
309 # Copy item to the new array
310 for e in array do typed_array.add e
311 return typed_array
312 end
313
314 # Uninferrable type, return as `Array[nullable Object]`
315 return array
316 end
317
318 return object
319 end
320
321 # Current array open for deserialization, used by `SimpleCollection::from_deserializer`
322 private var opened_array: nullable Array[nullable Object] = null
323
324 redef fun deserialize(static_type)
325 do
326 errors.clear
327 return convert_object(root, static_type)
328 end
329
330 # User customizable heuristic to infer the name of the Nit class to deserialize `json_object`
331 #
332 # This method is called only when deserializing an object without the metadata `__class`.
333 # Use the content of `json_object` to identify what Nit class it should be deserialized into.
334 # Or use `self.attributes_path` indicating where the deserialized object will be stored,
335 # is is less reliable as some objects don't have an associated attribute:
336 # the root/first deserialized object and collection elements.
337 #
338 # Return the class name as a `String` when it can be inferred,
339 # or `null` when the class name cannot be found.
340 #
341 # If a valid class name is returned, `json_object` will then be deserialized normally.
342 # So it must contain the attributes of the corresponding class, as usual.
343 #
344 # ~~~
345 # class MyData
346 # serialize
347 #
348 # var data: String
349 # end
350 #
351 # class MyError
352 # serialize
353 #
354 # var error: String
355 # var related_data: MyData
356 # end
357 #
358 # class MyJsonDeserializer
359 # super JsonDeserializer
360 #
361 # redef fun class_name_heuristic(json_object)
362 # do
363 # # Infer the Nit class from the content of the JSON object.
364 # if json_object.keys.has("error") then return "MyError"
365 # if json_object.keys.has("data") then return "MyData"
366 #
367 # # Infer the Nit class from the attribute where it will be stored.
368 # # This line duplicates a previous line, and would only apply when
369 # # `MyData` is within a `MyError`.
370 # if attributes_path.not_empty and attributes_path.last == "related_data" then return "MyData"
371 #
372 # return null
373 # end
374 # end
375 #
376 # var json = """{"data": "some data"}"""
377 # var deserializer = new MyJsonDeserializer(json)
378 # var deserialized = deserializer.deserialize
379 # assert deserializer.errors.is_empty
380 # assert deserialized isa MyData
381 #
382 # json = """{"error": "some error message",
383 # "related_data": {"data": "some other data"}}"""
384 # deserializer = new MyJsonDeserializer(json)
385 # deserialized = deserializer.deserialize
386 # assert deserializer.errors.is_empty
387 # assert deserialized isa MyError
388 # ~~~
389 protected fun class_name_heuristic(json_object: Map[String, nullable Object]): nullable String
390 do
391 return null
392 end
393 end
394
395 redef class Text
396
397 # Deserialize a `nullable Object` from this JSON formatted string
398 #
399 # Warning: Deserialization errors are reported with `print_error` and
400 # may be returned as a partial object or as `null`.
401 #
402 # This method is not appropriate when errors need to be handled programmatically,
403 # manually use a `JsonDeserializer` in such cases.
404 fun from_json_string: nullable Object
405 do
406 var deserializer = new JsonDeserializer(self)
407 var res = deserializer.deserialize
408 if deserializer.errors.not_empty then
409 print_error "Deserialization Errors: {deserializer.errors.join(", ")}"
410 end
411 return res
412 end
413
414 # Strip the `nullable` prefix and the params from the class name `self`
415 #
416 # ~~~nitish
417 # assert "String".strip_nullable_and_params == "String"
418 # assert "Array[Int]".strip_nullable_and_params == "Array"
419 # assert "Map[Set[String], Set[Int]]".strip_nullable_and_params == "Map"
420 # ~~~
421 private fun strip_nullable_and_params: String
422 do
423 var class_name = to_s
424
425 var prefix = "nullable "
426 if class_name.has_prefix(prefix) then class_name = class_name.substring_from(prefix.length)
427
428 var bracket_index = class_name.index_of('[')
429 if bracket_index == -1 then return class_name
430 return class_name.substring(0, bracket_index)
431 end
432 end
433
434 redef class SimpleCollection[E]
435 redef init from_deserializer(v)
436 do
437 super
438 if v isa JsonDeserializer then
439 v.notify_of_creation self
440 init
441
442 var open_array: nullable SequenceRead[nullable Object] = v.opened_array
443 if open_array == null then
444 # With metadata
445 var arr = v.path.last.get_or_null("__items")
446 if not arr isa SequenceRead[nullable Object] then
447 # If there is nothing, we consider that it is an empty collection.
448 if arr != null then v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
449 return
450 end
451 open_array = arr
452 end
453
454 # Try to get the name of the single parameter type assuming it is E.
455 # This does not work in non-generic subclasses,
456 # when the first parameter is not E, or
457 # when there is more than one parameter. (The last one could be fixed)
458 var class_name = class_name
459 var items_type = null
460 var bracket_index = class_name.index_of('[')
461 if bracket_index != -1 then
462 var start = bracket_index + 1
463 var ending = class_name.last_index_of(']')
464 items_type = class_name.substring(start, ending-start)
465 end
466
467 # Fill array
468 for o in open_array do
469 var obj = v.convert_object(o, items_type)
470 if obj isa E then
471 add obj
472 else v.errors.add new AttributeTypeError(self, "items", obj, "E")
473 end
474 end
475 end
476 end
477
478 redef class Map[K, V]
479 redef init from_deserializer(v)
480 do
481 super
482
483 if v isa JsonDeserializer then
484 v.notify_of_creation self
485 init
486
487 var length = v.deserialize_attribute("__length")
488 var keys = v.path.last.get_or_null("__keys")
489 var values = v.path.last.get_or_null("__values")
490
491 # Length is optional
492 if length == null and keys isa SequenceRead[nullable Object] then length = keys.length
493
494 # Consistency check
495 if not length isa Int or length < 0 or
496 not keys isa SequenceRead[nullable Object] or
497 not values isa SequenceRead[nullable Object] or
498 keys.length != values.length or length != keys.length then
499
500 # If there is nothing or length == 0, we consider that it is an empty Map.
501 if (length != null and length != 0) or keys != null or values != null then
502 v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
503 end
504 return
505 end
506
507 for i in length.times do
508 var key = v.convert_object(keys[i])
509 var value = v.convert_object(values[i])
510
511 if not key isa K then
512 v.errors.add new AttributeTypeError(self, "keys", key, "K")
513 continue
514 end
515
516 if not value isa V then
517 v.errors.add new AttributeTypeError(self, "values", value, "V")
518 continue
519 end
520
521 if has_key(key) then
522 v.errors.add new Error("Deserialization Error: duplicated key '{key or else "null"}' in {self.class_name}, previous value overwritten")
523 end
524
525 self[key] = value
526 end
527 end
528 end
529 end
530
531 # ---
532 # Metamodel
533
534 # Class inheritance graph as a `POSet[String]` serialized to JSON
535 private fun class_inheritance_metamodel_json: NativeString is intern
536
537 redef class Sys
538 # Class inheritance graph
539 #
540 # ~~~
541 # var hierarchy = class_inheritance_metamodel
542 # assert hierarchy.has_edge("String", "Object")
543 # assert not hierarchy.has_edge("Object", "String")
544 # ~~~
545 var class_inheritance_metamodel: POSet[String] is lazy do
546 var engine = new JsonDeserializer(class_inheritance_metamodel_json.to_s)
547 engine.check_subtypes = false
548 engine.whitelist.add_all(
549 ["String", "POSet[String]", "POSetElement[String]", "HashSet[String]", "HashMap[String, POSetElement[String]]"])
550 var poset = engine.deserialize
551 if engine.errors.not_empty then
552 print_error engine.errors.join("\n")
553 return new POSet[String]
554 end
555 if poset isa POSet[String] then return poset
556 return new POSet[String]
557 end
558 end
559
560 redef class Deserializer
561 redef fun deserialize_class(name)
562 do
563 if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
564 if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
565 if name == "HashSet[String]" then return new HashSet[String].from_deserializer(self)
566 if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
567
568 return super
569 end
570 end