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: `deserialize_json` and `JsonDeserializer`
16 module serialization_read
18 import serialization
::caching
19 private import serialization
::engine_tools
20 import serialization
::safe
25 # Deserializer from a Json string.
26 class JsonDeserializer
27 super CachingDeserializer
28 super SafeDeserializer
30 # Json text to deserialize from.
31 private var text
: Text
33 # Root json object parsed from input text.
34 private var root
: nullable Object is noinit
36 # Depth-first path in the serialized object tree.
37 private var path
= new Array[Map[String, nullable Object]]
39 # Names of the attributes from the root to the object currently being deserialized
40 var attributes_path
= new Array[String]
42 # Last encountered object reference id.
45 var just_opened_id
: nullable Int = null
48 var root
= text
.parse_json
49 if root
isa Map[String, nullable Object] then path
.add
(root
)
53 redef fun deserialize_attribute
(name
, static_type
)
56 # The was a parsing error or the root is not an object
57 if not root
isa Error then
58 errors
.add
new Error("Deserialization Error: parsed JSON value is not an object.")
60 deserialize_attribute_missing
= false
64 var current
= path
.last
66 if not current
.keys
.has
(name
) then
67 # Let the generated code / caller of `deserialize_attribute` raise the missing attribute error
68 deserialize_attribute_missing
= true
72 var value
= current
[name
]
74 attributes_path
.add name
75 var res
= convert_object
(value
, static_type
)
78 deserialize_attribute_missing
= false
82 # This may be called multiple times by the same object from constructors
83 # in different nclassdef
84 redef fun notify_of_creation
(new_object
)
86 var id
= just_opened_id
87 if id
== null then return # Register `new_object` only once
88 cache
[id
] = new_object
91 # Convert the simple JSON `object` to a Nit object
92 private fun convert_object
(object
: nullable Object, static_type
: nullable String): nullable Object
94 if object
isa JsonParseError then
99 if object
isa Map[String, nullable Object] then
101 if object
.keys
.has
("__kind") then
102 kind
= object
["__kind"]
106 if kind
== "ref" then
107 if not object
.keys
.has
("__id") then
108 errors
.add
new Error("Deserialization Error: JSON object reference does not declare a `__id`.")
112 var id
= object
["__id"]
113 if not id
isa Int then
114 errors
.add
new Error("Deserialization Error: JSON object reference declares a non-integer `__id`.")
118 if not cache
.has_id
(id
) then
119 errors
.add
new Error("Deserialization Error: JSON object reference has an unknown `__id`.")
123 return cache
.object_for
(id
)
127 if kind
== "obj" or kind
== null then
129 if object
.keys
.has
("__id") then
132 if not id
isa Int then
133 errors
.add
new Error("Deserialization Error: JSON object declaration declares a non-integer `__id`.")
137 if cache
.has_id
(id
) then
138 errors
.add
new Error("Deserialization Error: JSON object with `__id` {id} is deserialized twice.")
143 var class_name
= object
.get_or_null
("__class")
144 if class_name
== null then
145 # Fallback to custom heuristic
146 class_name
= class_name_heuristic
(object
)
148 if class_name
== null and static_type
!= null then
149 # Fallack to the static type, strip the `nullable` prefix
150 class_name
= static_type
.strip_nullable
154 if class_name
== null then
155 errors
.add
new Error("Deserialization Error: JSON object declaration does not declare a `__class`.")
159 if not class_name
isa String then
160 errors
.add
new Error("Deserialization Error: JSON object declaration declares a non-string `__class`.")
164 if not accept
(class_name
, static_type
) then return null
170 var value
= deserialize_class
(class_name
)
171 just_opened_id
= null
180 if kind
== "char" then
181 if not object
.keys
.has
("__val") then
182 errors
.add
new Error("Deserialization Error: JSON `char` object does not declare a `__val`.")
186 var val
= object
["__val"]
188 if not val
isa String or val
.is_empty
then
189 errors
.add
new Error("Deserialization Error: JSON `char` object does not declare a single char in `__val`.")
193 return val
.chars
.first
197 if kind
== "byte" then
198 var val
= object
.get_or_null
("__val")
199 if not val
isa Int then
200 errors
.add
new Error("Serialization Error: JSON `byte` object does not declare an integer `__val`.")
207 errors
.add
new Error("Deserialization Error: JSON object has an unknown `__kind`.")
211 # Simple JSON array without serialization metadata
212 if object
isa Array[nullable Object] then
213 # Can we use the static type?
214 if static_type
!= null then
215 opened_array
= object
216 var value
= deserialize_class
(static_type
.strip_nullable
)
221 # This branch should rarely be used:
222 # when an array is the root object which is accepted but illegal in standard JSON,
223 # or in strange custom deserialization hacks.
225 var array
= new Array[nullable Object]
226 var types
= new HashSet[String]
227 var has_nullable
= false
229 var res
= convert_object
(e
)
233 types
.add res
.class_name
234 else has_nullable
= true
237 if types
.length
== 1 then
238 var array_type
= types
.first
241 if array_type
== "ASCIIFlatString" or array_type
== "UnicodeFlatString" then
243 typed_array
= new Array[nullable FlatString]
244 else typed_array
= new Array[FlatString]
245 else if array_type
== "Int" then
247 typed_array
= new Array[nullable Int]
248 else typed_array
= new Array[Int]
249 else if array_type
== "Float" then
251 typed_array
= new Array[nullable Float]
252 else typed_array
= new Array[Float]
254 # TODO support all array types when we separate the constructor
255 # `from_deserializer` from the filling of the items.
257 if not has_nullable
then
258 typed_array
= new Array[Object]
260 # Unsupported array type, return as `Array[nullable Object]`
265 assert typed_array
isa Array[nullable Object]
267 # Copy item to the new array
268 for e
in array
do typed_array
.add e
272 # Uninferrable type, return as `Array[nullable Object]`
276 if object
isa String and object
.length
== 1 and static_type
== "Char" then
277 # Char serialized as a JSON string
278 return object
.chars
.first
281 if object
isa Int and static_type
== "Byte" then
282 # Byte serialized as an integer
289 # Current array open for deserialization, used by `SimpleCollection::from_deserializer`
290 private var opened_array
: nullable Array[nullable Object] = null
292 redef fun deserialize
(static_type
)
295 return convert_object
(root
, static_type
)
298 # User customizable heuristic to infer the name of the Nit class to deserialize `json_object`
300 # This method is called only when deserializing an object without the metadata `__class`.
301 # Use the content of `json_object` to identify what Nit class it should be deserialized into.
302 # Or use `self.attributes_path` indicating where the deserialized object will be stored,
303 # is is less reliable as some objects don't have an associated attribute:
304 # the root/first deserialized object and collection elements.
306 # Return the class name as a `String` when it can be inferred,
307 # or `null` when the class name cannot be found.
309 # If a valid class name is returned, `json_object` will then be deserialized normally.
310 # So it must contain the attributes of the corresponding class, as usual.
323 # var related_data: MyData
326 # class MyJsonDeserializer
327 # super JsonDeserializer
329 # redef fun class_name_heuristic(json_object)
331 # # Infer the Nit class from the content of the JSON object.
332 # if json_object.keys.has("error") then return "MyError"
333 # if json_object.keys.has("data") then return "MyData"
335 # # Infer the Nit class from the attribute where it will be stored.
336 # # This line duplicates a previous line, and would only apply when
337 # # `MyData` is within a `MyError`.
338 # if attributes_path.not_empty and attributes_path.last == "related_data" then return "MyData"
344 # var json = """{"data": "some data"}"""
345 # var deserializer = new MyJsonDeserializer(json)
346 # var deserialized = deserializer.deserialize
347 # assert deserializer.errors.is_empty
348 # assert deserialized isa MyData
350 # json = """{"error": "some error message",
351 # "related_data": {"data": "some other data"}}"""
352 # deserializer = new MyJsonDeserializer(json)
353 # deserialized = deserializer.deserialize
354 # assert deserializer.errors.is_empty
355 # assert deserialized isa MyError
357 protected fun class_name_heuristic
(json_object
: Map[String, nullable Object]): nullable String
365 # Deserialize a `nullable Object` from this JSON formatted string
367 # If a `static_type` is given, only subtypes of the `static_type` are accepted.
369 # Warning: Deserialization errors are reported with `print_error` and
370 # may be returned as a partial object or as `null`.
372 # This method is not appropriate when errors need to be handled programmatically,
373 # manually use a `JsonDeserializer` in such cases.
374 fun deserialize_json
(static_type
: nullable String): nullable Object
376 var deserializer
= new JsonDeserializer(self)
377 var res
= deserializer
.deserialize
(static_type
)
378 if deserializer
.errors
.not_empty
then
379 print_error
"Deserialization Errors: {deserializer.errors.join(", ")}"
385 redef class SimpleCollection[E
]
386 redef init from_deserializer
(v
)
389 if v
isa JsonDeserializer then
390 v
.notify_of_creation
self
393 var open_array
: nullable SequenceRead[nullable Object] = v
.opened_array
394 if open_array
== null then
396 var arr
= v
.path
.last
.get_or_null
("__items")
397 if not arr
isa SequenceRead[nullable Object] then
398 # If there is nothing, we consider that it is an empty collection.
399 if arr
!= null then v
.errors
.add
new Error("Deserialization Error: invalid format in {self.class_name}")
405 # Name of the dynamic name of E
406 var items_type_name
= (new GetName[E
]).to_s
409 for o
in open_array
do
410 var obj
= v
.convert_object
(o
, items_type_name
)
413 else v
.errors
.add
new AttributeTypeError(self, "items", obj
, items_type_name
)
419 redef class Map[K
, V
]
420 redef init from_deserializer
(v
)
424 if v
isa JsonDeserializer then
425 v
.notify_of_creation
self
428 var keys_type_name
= (new GetName[K
]).to_s
429 var values_type_name
= (new GetName[V
]).to_s
431 var length
= v
.deserialize_attribute
("__length")
432 var keys
= v
.path
.last
.get_or_null
("__keys")
433 var values
= v
.path
.last
.get_or_null
("__values")
435 if keys
== null and values
== null then
436 # Fallback to a plain object
437 for key
, value_src
in v
.path
.last
do
439 var value
= v
.convert_object
(value_src
, values_type_name
)
441 if not key
isa K
then
442 v
.errors
.add
new AttributeTypeError(self, "keys", key
, keys_type_name
)
446 if not value
isa V
then
447 v
.errors
.add
new AttributeTypeError(self, "values", value
, values_type_name
)
457 if length
== null and keys
isa SequenceRead[nullable Object] then length
= keys
.length
460 if not length
isa Int or length
< 0 or
461 not keys
isa SequenceRead[nullable Object] or
462 not values
isa SequenceRead[nullable Object] or
463 keys
.length
!= values
.length
or length
!= keys
.length
then
465 # If there is nothing or length == 0, we consider that it is an empty Map.
466 if (length
!= null and length
!= 0) or keys
!= null or values
!= null then
467 v
.errors
.add
new Error("Deserialization Error: invalid format in {self.class_name}")
472 # First, convert all keys to follow the order of the serialization
473 var converted_keys
= new Array[K
]
474 for i
in length
.times
do
475 var key
= v
.convert_object
(keys
[i
], keys_type_name
)
477 if not key
isa K
then
478 v
.errors
.add
new AttributeTypeError(self, "keys", key
, keys_type_name
)
482 converted_keys
.add key
485 # Then convert the values and build the map
486 for i
in length
.times
do
487 var key
= converted_keys
[i
]
488 var value
= v
.convert_object
(values
[i
], values_type_name
)
490 if not value
isa V
then
491 v
.errors
.add
new AttributeTypeError(self, "values", value
, values_type_name
)
496 v
.errors
.add
new Error("Deserialization Error: duplicated key '{key or else "null"}' in {self.class_name}, previous value overwritten")
508 # Class inheritance graph as a `POSet[String]` serialized to JSON
509 private fun class_inheritance_metamodel_json
: CString is intern
512 redef var class_inheritance_metamodel
is lazy
do
513 var engine
= new JsonDeserializer(class_inheritance_metamodel_json
.to_s
)
514 engine
.check_subtypes
= false
515 engine
.whitelist
.add_all
(
516 ["String", "POSet[String]", "POSetElement[String]",
517 "HashSet[String]", "HashMap[String, POSetElement[String]]"])
519 var poset
= engine
.deserialize
520 if engine
.errors
.not_empty
then
521 print_error
"Deserialization errors in class_inheritance_metamodel:"
522 print_error engine
.errors
.join
("\n* ")
523 return new POSet[String]
526 if poset
isa POSet[String] then return poset
527 return new POSet[String]