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