1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Services to read JSON: `from_json_string` and `JsonDeserializer`
16 module serialization_read
18 import ::serialization
::caching
19 private import ::serialization
::engine_tools
21 private import string_parser
24 # Deserializer from a Json string.
25 class JsonDeserializer
26 super CachingDeserializer
28 # Json text to deserialize from.
29 private var text
: Text
31 # Accepted parameterized classes to deserialize
33 # If `whitelist.empty`, all types are accepted.
36 # import json::serialization
42 # var json_string = """
43 # {"__class" = "MyClass"}
46 # var deserializer = new JsonDeserializer(json_string)
47 # var obj = deserializer.deserialize
48 # assert deserializer.errors.is_empty
49 # assert obj isa MyClass
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
58 var whitelist
= new Array[Text]
60 # Should objects be checked if they a subtype of the static type before deserialization?
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
66 # Root json object parsed from input text.
67 private var root
: nullable Object is noinit
69 # Depth-first path in the serialized object tree.
70 private var path
= new Array[Map[String, nullable Object]]
72 # Names of the attributes from the root to the object currently being deserialized
73 var attributes_path
= new Array[String]
75 # Last encountered object reference id.
78 var just_opened_id
: nullable Int = null
81 var root
= text
.parse_json
82 if root
isa Map[String, nullable Object] then path
.add
(root
)
86 redef fun deserialize_attribute
(name
, static_type
)
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.")
93 deserialize_attribute_missing
= false
97 var current
= path
.last
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
105 var value
= current
[name
]
107 attributes_path
.add name
108 var res
= convert_object
(value
, static_type
)
111 deserialize_attribute_missing
= false
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
)
119 var id
= just_opened_id
120 if id
== null then return # Register `new_object` only once
121 cache
[id
] = new_object
124 # Convert the simple JSON `object` to a Nit object
125 private fun convert_object
(object
: nullable Object, static_type
: nullable String): nullable Object
127 if object
isa JsonParseError then
132 if object
isa Map[String, nullable Object] then
134 if object
.keys
.has
("__kind") then
135 kind
= object
["__kind"]
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`.")
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`.")
151 if not cache
.has_id
(id
) then
152 errors
.add
new Error("Deserialization Error: JSON object reference has an unknown `__id`.")
156 return cache
.object_for
(id
)
160 if kind
== "obj" or kind
== null then
162 if object
.keys
.has
("__id") then
165 if not id
isa Int then
166 errors
.add
new Error("Deserialization Error: JSON object declaration declares a non-integer `__id`.")
170 if cache
.has_id
(id
) then
171 errors
.add
new Error("Deserialization Error: JSON object with `__id` {id} is deserialized twice.")
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
)
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
190 if class_name
== null then
191 errors
.add
new Error("Deserialization Error: JSON object declaration does not declare a `__class`.")
195 if not class_name
isa String then
196 errors
.add
new Error("Deserialization Error: JSON object declaration declares a non-string `__class`.")
200 if whitelist
.not_empty
and not whitelist
.has
(class_name
) then
201 errors
.add
new Error("Deserialization Error: '{class_name}' not in whitelist")
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}`")
218 var value
= deserialize_class
(class_name
)
219 just_opened_id
= null
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`.")
234 var val
= object
["__val"]
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`.")
241 return val
.chars
.first
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`.")
255 errors
.add
new Error("Deserialization Error: JSON object has an unknown `__kind`.")
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
)
268 opened_array
= object
269 var value
= deserialize_class
(class_name
)
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.
278 var array
= new Array[nullable Object]
279 var types
= new HashSet[String]
280 var has_nullable
= false
282 var res
= convert_object
(e
)
286 types
.add res
.class_name
287 else has_nullable
= true
290 if types
.length
== 1 then
291 var array_type
= types
.first
294 if array_type
== "ASCIIFlatString" or array_type
== "UnicodeFlatString" then
296 typed_array
= new Array[nullable FlatString]
297 else typed_array
= new Array[FlatString]
298 else if array_type
== "Int" then
300 typed_array
= new Array[nullable Int]
301 else typed_array
= new Array[Int]
302 else if array_type
== "Float" then
304 typed_array
= new Array[nullable Float]
305 else typed_array
= new Array[Float]
307 # TODO support all array types when we separate the constructor
308 # `from_deserializer` from the filling of the items.
310 if not has_nullable
then
311 typed_array
= new Array[Object]
313 # Unsupported array type, return as `Array[nullable Object]`
318 assert typed_array
isa Array[nullable Object]
320 # Copy item to the new array
321 for e
in array
do typed_array
.add e
325 # Uninferrable type, return as `Array[nullable Object]`
332 # Current array open for deserialization, used by `SimpleCollection::from_deserializer`
333 private var opened_array
: nullable Array[nullable Object] = null
335 redef fun deserialize
(static_type
)
338 return convert_object
(root
, static_type
)
341 # User customizable heuristic to infer the name of the Nit class to deserialize `json_object`
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.
349 # Return the class name as a `String` when it can be inferred,
350 # or `null` when the class name cannot be found.
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.
366 # var related_data: MyData
369 # class MyJsonDeserializer
370 # super JsonDeserializer
372 # redef fun class_name_heuristic(json_object)
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"
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"
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
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
400 protected fun class_name_heuristic
(json_object
: Map[String, nullable Object]): nullable String
408 # Deserialize a `nullable Object` from this JSON formatted string
410 # Warning: Deserialization errors are reported with `print_error` and
411 # may be returned as a partial object or as `null`.
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
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(", ")}"
425 # Strip the `nullable` prefix and the params from the class name `self`
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"
432 private fun strip_nullable_and_params
: String
434 var class_name
= to_s
436 var prefix
= "nullable "
437 if class_name
.has_prefix
(prefix
) then class_name
= class_name
.substring_from
(prefix
.length
)
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
)
445 redef class SimpleCollection[E
]
446 redef init from_deserializer
(v
)
449 if v
isa JsonDeserializer then
450 v
.notify_of_creation
self
453 var open_array
: nullable SequenceRead[nullable Object] = v
.opened_array
454 if open_array
== null then
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}")
465 # Name of the dynamic name of E
466 var items_type_name
= (new GetName[E
]).to_s
469 for o
in open_array
do
470 var obj
= v
.convert_object
(o
, items_type_name
)
473 else v
.errors
.add
new AttributeTypeError(self, "items", obj
, items_type_name
)
479 redef class Map[K
, V
]
480 redef init from_deserializer
(v
)
484 if v
isa JsonDeserializer then
485 v
.notify_of_creation
self
488 var keys_type_name
= (new GetName[K
]).to_s
489 var values_type_name
= (new GetName[V
]).to_s
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")
495 if keys
== null and values
== null then
496 # Fallback to a plain object
497 for key
, value_src
in v
.path
.last
do
499 var value
= v
.convert_object
(value_src
, values_type_name
)
501 if not key
isa K
then
502 v
.errors
.add
new AttributeTypeError(self, "keys", key
, keys_type_name
)
506 if not value
isa V
then
507 v
.errors
.add
new AttributeTypeError(self, "values", value
, values_type_name
)
517 if length
== null and keys
isa SequenceRead[nullable Object] then length
= keys
.length
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
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}")
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
)
537 if not key
isa K
then
538 v
.errors
.add
new AttributeTypeError(self, "keys", key
, keys_type_name
)
542 converted_keys
.add key
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
)
550 if not value
isa V
then
551 v
.errors
.add
new AttributeTypeError(self, "values", value
, values_type_name
)
556 v
.errors
.add
new Error("Deserialization Error: duplicated key '{key or else "null"}' in {self.class_name}, previous value overwritten")
568 # Class inheritance graph as a `POSet[String]` serialized to JSON
569 private fun class_inheritance_metamodel_json
: CString is intern
572 # Class inheritance graph
575 # var hierarchy = class_inheritance_metamodel
576 # assert hierarchy.has_edge("String", "Object")
577 # assert not hierarchy.has_edge("Object", "String")
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]
589 if poset
isa POSet[String] then return poset
590 return new POSet[String]
594 redef class Deserializer
595 redef fun deserialize_class
(name
)
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)