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
23 # Deserializer from a Json string.
24 class JsonDeserializer
25 super CachingDeserializer
27 # Json text to deserialize from.
28 private var text
: Text
30 # Root json object parsed from input text.
31 private var root
: nullable Object is noinit
33 # Depth-first path in the serialized object tree.
34 private var path
= new Array[Map[String, nullable Object]]
36 # Names of the attributes from the root to the object currently being deserialized
37 var attributes_path
= new Array[String]
39 # Last encountered object reference id.
42 var just_opened_id
: nullable Int = null
45 var root
= text
.parse_json
46 if root
isa Map[String, nullable Object] then path
.add
(root
)
50 redef fun deserialize_attribute
(name
, static_type
)
53 # The was a parsing error or the root is not an object
54 if not root
isa Error then
55 errors
.add
new Error("Deserialization Error: parsed JSON value is not an object.")
57 deserialize_attribute_missing
= false
61 var current
= path
.last
63 if not current
.keys
.has
(name
) then
64 # Let the generated code / caller of `deserialize_attribute` raise the missing attribute error
65 deserialize_attribute_missing
= true
69 var value
= current
[name
]
71 attributes_path
.add name
72 var res
= convert_object
(value
, static_type
)
75 deserialize_attribute_missing
= false
79 # This may be called multiple times by the same object from constructors
80 # in different nclassdef
81 redef fun notify_of_creation
(new_object
)
83 var id
= just_opened_id
84 if id
== null then return # Register `new_object` only once
85 cache
[id
] = new_object
88 # Convert the simple JSON `object` to a Nit object
89 private fun convert_object
(object
: nullable Object, static_type
: nullable String): nullable Object
91 if object
isa JsonParseError then
96 if object
isa Map[String, nullable Object] then
98 if object
.keys
.has
("__kind") then
99 kind
= object
["__kind"]
103 if kind
== "ref" then
104 if not object
.keys
.has
("__id") then
105 errors
.add
new Error("Serialization Error: JSON object reference does not declare a `__id`.")
109 var id
= object
["__id"]
110 if not id
isa Int then
111 errors
.add
new Error("Serialization Error: JSON object reference declares a non-integer `__id`.")
115 if not cache
.has_id
(id
) then
116 errors
.add
new Error("Serialization Error: JSON object reference has an unknown `__id`.")
120 return cache
.object_for
(id
)
124 if kind
== "obj" or kind
== null then
126 if object
.keys
.has
("__id") then
129 if not id
isa Int then
130 errors
.add
new Error("Serialization Error: JSON object declaration declares a non-integer `__id`.")
134 if cache
.has_id
(id
) then
135 errors
.add
new Error("Serialization Error: JSON object with `__id` {id} is deserialized twice.")
140 var class_name
= object
.get_or_null
("__class")
141 if class_name
== null then
142 # Fallback to custom heuristic
143 class_name
= class_name_heuristic
(object
)
145 if class_name
== null and static_type
!= null then
146 # Fallack to the static type, strip the `nullable` prefix
147 var prefix
= "nullable "
148 if static_type
.has
(prefix
) then
149 class_name
= static_type
.substring_from
(prefix
.length
)
150 else class_name
= static_type
154 if class_name
== null then
155 errors
.add
new Error("Serialization Error: JSON object declaration does not declare a `__class`.")
159 if not class_name
isa String then
160 errors
.add
new Error("Serialization Error: JSON object declaration declares a non-string `__class`.")
168 var value
= deserialize_class
(class_name
)
169 just_opened_id
= null
178 if kind
== "char" then
179 if not object
.keys
.has
("__val") then
180 errors
.add
new Error("Serialization Error: JSON `char` object does not declare a `__val`.")
184 var val
= object
["__val"]
186 if not val
isa String or val
.is_empty
then
187 errors
.add
new Error("Serialization Error: JSON `char` object does not declare a single char in `__val`.")
191 return val
.chars
.first
194 errors
.add
new Error("Serialization Error: JSON object has an unknown `__kind`.")
198 # Simple JSON array without serialization metadata
199 if object
isa Array[nullable Object] then
200 # Can we use the static type?
201 if static_type
!= null then
202 var prefix
= "nullable "
203 var class_name
= if static_type
.has
(prefix
) then
204 static_type
.substring_from
(prefix
.length
)
207 opened_array
= object
208 var value
= deserialize_class
(class_name
)
213 # This branch should rarely be used:
214 # when an array is the root object which is accepted but illegal in standard JSON,
215 # or in strange custom deserialization hacks.
217 var array
= new Array[nullable Object]
218 var types
= new HashSet[String]
219 var has_nullable
= false
221 var res
= convert_object
(e
)
225 types
.add res
.class_name
226 else has_nullable
= true
229 if types
.length
== 1 then
230 var array_type
= types
.first
233 if array_type
== "ASCIIFlatString" or array_type
== "UnicodeFlatString" then
235 typed_array
= new Array[nullable FlatString]
236 else typed_array
= new Array[FlatString]
237 else if array_type
== "Int" then
239 typed_array
= new Array[nullable Int]
240 else typed_array
= new Array[Int]
241 else if array_type
== "Float" then
243 typed_array
= new Array[nullable Float]
244 else typed_array
= new Array[Float]
246 # TODO support all array types when we separate the constructor
247 # `from_deserializer` from the filling of the items.
249 if not has_nullable
then
250 typed_array
= new Array[Object]
252 # Unsupported array type, return as `Array[nullable Object]`
257 assert typed_array
isa Array[nullable Object]
259 # Copy item to the new array
260 for e
in array
do typed_array
.add e
264 # Uninferrable type, return as `Array[nullable Object]`
271 # Current array open for deserialization, used by `SimpleCollection::from_deserializer`
272 private var opened_array
: nullable Array[nullable Object] = null
274 redef fun deserialize
277 return convert_object
(root
)
280 # User customizable heuristic to infer the name of the Nit class to deserialize `json_object`
282 # This method is called only when deserializing an object without the metadata `__class`.
283 # Use the content of `json_object` to identify what Nit class it should be deserialized into.
284 # Or use `self.attributes_path` indicating where the deserialized object will be stored,
285 # is is less reliable as some objects don't have an associated attribute:
286 # the root/first deserialized object and collection elements.
288 # Return the class name as a `String` when it can be inferred,
289 # or `null` when the class name cannot be found.
291 # If a valid class name is returned, `json_object` will then be deserialized normally.
292 # So it must contain the attributes of the corresponding class, as usual.
305 # var related_data: MyData
308 # class MyJsonDeserializer
309 # super JsonDeserializer
311 # redef fun class_name_heuristic(json_object)
313 # # Infer the Nit class from the content of the JSON object.
314 # if json_object.keys.has("error") then return "MyError"
315 # if json_object.keys.has("data") then return "MyData"
317 # # Infer the Nit class from the attribute where it will be stored.
318 # # This line duplicates a previous line, and would only apply when
319 # # `MyData` is within a `MyError`.
320 # if attributes_path.not_empty and attributes_path.last == "related_data" then return "MyData"
326 # var json = """{"data": "some data"}"""
327 # var deserializer = new MyJsonDeserializer(json)
328 # var deserialized = deserializer.deserialize
329 # assert deserializer.errors.is_empty
330 # assert deserialized isa MyData
332 # json = """{"error": "some error message",
333 # "related_data": {"data": "some other data"}}"""
334 # deserializer = new MyJsonDeserializer(json)
335 # deserialized = deserializer.deserialize
336 # assert deserializer.errors.is_empty
337 # assert deserialized isa MyError
339 protected fun class_name_heuristic
(json_object
: Map[String, nullable Object]): nullable String
347 # Deserialize a `nullable Object` from this JSON formatted string
349 # Warning: Deserialization errors are reported with `print_error` and
350 # may be returned as a partial object or as `null`.
352 # This method is not appropriate when errors need to be handled programmatically,
353 # manually use a `JsonDeserializer` in such cases.
354 fun from_json_string
: nullable Object
356 var deserializer
= new JsonDeserializer(self)
357 var res
= deserializer
.deserialize
358 if deserializer
.errors
.not_empty
then
359 print_error
"Deserialization Errors: {deserializer.errors.join(", ")}"
365 redef class SimpleCollection[E
]
366 redef init from_deserializer
(v
)
369 if v
isa JsonDeserializer then
370 v
.notify_of_creation
self
373 var open_array
: nullable SequenceRead[nullable Object] = v
.opened_array
374 if open_array
== null then
376 var arr
= v
.path
.last
.get_or_null
("__items")
377 if not arr
isa SequenceRead[nullable Object] then
378 # If there is nothing, we consider that it is an empty collection.
379 if arr
!= null then v
.errors
.add
new Error("Deserialization Error: invalid format in {self.class_name}")
385 # Try to get the name of the single parameter type assuming it is E.
386 # This does not work in non-generic subclasses,
387 # when the first parameter is not E, or
388 # when there is more than one parameter. (The last one could be fixed)
389 var class_name
= class_name
390 var items_type
= null
391 var bracket_index
= class_name
.index_of
('[')
392 if bracket_index
!= -1 then
393 var start
= bracket_index
+ 1
394 var ending
= class_name
.last_index_of
(']')
395 items_type
= class_name
.substring
(start
, ending-start
)
399 for o
in open_array
do
400 var obj
= v
.convert_object
(o
, items_type
)
403 else v
.errors
.add
new AttributeTypeError(self, "items", obj
, "E")
409 redef class Map[K
, V
]
410 redef init from_deserializer
(v
)
414 if v
isa JsonDeserializer then
415 v
.notify_of_creation
self
418 var length
= v
.deserialize_attribute
("__length")
419 var keys
= v
.path
.last
.get_or_null
("__keys")
420 var values
= v
.path
.last
.get_or_null
("__values")
423 if length
== null and keys
isa SequenceRead[nullable Object] then length
= keys
.length
426 if not length
isa Int or length
< 0 or
427 not keys
isa SequenceRead[nullable Object] or
428 not values
isa SequenceRead[nullable Object] or
429 keys
.length
!= values
.length
or length
!= keys
.length
then
431 # If there is nothing or length == 0, we consider that it is an empty Map.
432 if (length
!= null and length
!= 0) or keys
!= null or values
!= null then
433 v
.errors
.add
new Error("Deserialization Error: invalid format in {self.class_name}")
438 for i
in length
.times
do
439 var key
= v
.convert_object
(keys
[i
])
440 var value
= v
.convert_object
(values
[i
])
442 if not key
isa K
then
443 v
.errors
.add
new AttributeTypeError(self, "keys", key
, "K")
447 if not value
isa V
then
448 v
.errors
.add
new AttributeTypeError(self, "values", value
, "V")
453 v
.errors
.add
new Error("Deserialization Error: duplicated key '{key or else "null"}' in {self.class_name}, previous value overwritten")