1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Handles serialization and deserialization of objects to/from JSON
21 # `JsonSerializer` write Nit objects that subclass `Serializable` to JSON,
22 # and `JsonDeserializer` can read them. They both use meta-data added to the
23 # generated JSON to recreate the Nit instances with the exact original type.
25 # For more information on Nit serialization, see: ../serialization/README.md
29 # The attribute `JsonSerializer::plain_json` triggers generating plain and
30 # clean JSON. This format is easier to read for an human and a non-Nit program,
31 # but it cannot be fully deserialized. It can still be read by services from
32 # `json::static` and `json::dynamic`.
34 # A shortcut to this service is provided by `Serializable::to_plain_json`.
39 # import json::serialization
45 # var year_of_birth: Int
46 # var next_of_kin: nullable Person
49 # var bob = new Person("Bob", 1986)
50 # var alice = new Person("Alice", 1978, bob)
52 # assert bob.to_plain_json == """
53 # {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}"""
55 # assert alice.to_plain_json == """
56 # {"name": "Alice", "year_of_birth": 1978, "next_of_kin": {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}}"""
59 # ## JSON to Nit objects
61 # The `JsonDeserializer` support reading JSON code with minimal meta-data
62 # to easily create Nit object from client-side code or configuration files.
63 # Each JSON object must define the `__class` attribute with the corresponding
64 # Nit class and the expected attributes with its name in Nit followed by its value.
69 # import json::serialization
74 # var description: String
75 # var max_participants: nullable Int
76 # var answers: Array[FlatString]
80 # {"__class": "MeetupConfig", "description": "My Awesome Meetup", "max_participants": null, "answers": ["Pepperoni", "Chicken"]}"""
81 # var deserializer = new JsonDeserializer(json_code)
83 # var meet = deserializer.deserialize
84 # assert meet isa MeetupConfig
85 # assert meet.description == "My Awesome Meetup"
86 # assert meet.max_participants == null
87 # assert meet.answers == ["Pepperoni", "Chicken"]
91 import ::serialization
::caching
92 private import ::serialization
::engine_tools
95 # Serializer of Nit objects to Json string.
97 super CachingSerializer
99 # Target writing stream
102 # Write plain JSON? easier to read but does not support Nit deserialization
104 # If `false`, the default, serialize to support deserialization:
106 # * Write meta-data, including the types of the serialized objects so they can
107 # be deserialized to their original form using `JsonDeserializer`.
108 # * Use references when an object has already been serialized so to not duplicate it.
109 # * Support cycles in references.
110 # * Preserve the Nit `Char` type as an object because it does not exist in JSON.
111 # * The generated JSON is standard and can be read by non-Nit programs.
112 # However, some Nit types are not represented by the simplest possible JSON representation.
113 # With the added meta-data, it can be complex to read.
115 # If `true`, serialize for other programs:
117 # * Nit objects are serialized to pure and standard JSON so they can
118 # be easily read by non-Nit programs and humans.
119 # * Nit objects are serialized for every references, so they can be duplicated.
120 # It is easier to read but it creates a larger output.
121 # * Does not support cycles, will replace the problematic references by `null`.
122 # * Does not serialize the meta-data needed to deserialize the objects
123 # back to regular Nit objects.
124 # * Keys of Nit `HashMap` are converted to their string reprensentation using `to_s`.
125 var plain_json
= false is writable
127 # List of the current open objects, the first is the main target of the serialization
129 # Used only when `plain_json == true` to detect cycles in serialization.
130 private var open_objects
= new Array[Object]
132 # Has the first attribute of the current object already been serialized?
134 # Used only when `plain_json == true`.
135 private var first_attribute
= false
137 redef fun serialize
(object
)
139 if object
== null then
143 for o
in open_objects
do
144 if object
.is_same_serialized
(o
) then
151 open_objects
.add object
154 first_attribute
= true
155 object
.serialize_to_json
self
156 first_attribute
= false
158 if plain_json
then open_objects
.pop
162 redef fun serialize_attribute
(name
, value
)
164 if not plain_json
or not first_attribute
then
166 first_attribute
= false
175 redef fun serialize_reference
(object
)
177 if not plain_json
and cache
.has_object
(object
) then
178 # if already serialized, add local reference
179 var id
= cache
.id_for
(object
)
180 stream
.write
"\{\"__kind\
": \"ref\
", \"__id\
": "
190 # Deserializer from a Json string.
191 class JsonDeserializer
192 super CachingDeserializer
194 # Json text to deserialize from.
195 private var text
: Text
197 # Root json object parsed from input text.
198 private var root
: nullable Jsonable is noinit
200 # Depth-first path in the serialized object tree.
201 private var path
= new Array[JsonObject]
203 # Last encountered object reference id.
205 # See `id_to_object`.
206 var just_opened_id
: nullable Int = null
209 var root
= text
.parse_json
210 if root
isa JsonObject then path
.add
(root
)
214 redef fun deserialize_attribute
(name
)
216 assert not path
.is_empty
# This is an internal error, abort
217 var current
= path
.last
219 if not current
.keys
.has
(name
) then
220 errors
.add
new Error("Deserialization Error: JSON object has not attribute '{name}'.")
224 var value
= current
[name
]
226 return convert_object
(value
)
229 # This may be called multiple times by the same object from constructors
230 # in different nclassdef
231 redef fun notify_of_creation
(new_object
)
233 var id
= just_opened_id
234 if id
== null then return # Register `new_object` only once
235 cache
[id
] = new_object
238 # Convert from simple Json object to Nit object
239 private fun convert_object
(object
: nullable Object): nullable Object
241 if object
isa JsonParseError then
246 if object
isa JsonObject then
248 if object
.keys
.has
("__kind") then
249 kind
= object
["__kind"]
253 if kind
== "ref" then
254 if not object
.keys
.has
("__id") then
255 errors
.add
new Error("Serialization Error: JSON object reference does not declare a `__id`.")
259 var id
= object
["__id"]
260 if not id
isa Int then
261 errors
.add
new Error("Serialization Error: JSON object reference declares a non-integer `__id`.")
265 if not cache
.has_id
(id
) then
266 errors
.add
new Error("Serialization Error: JSON object reference has an unknown `__id`.")
270 return cache
.object_for
(id
)
274 if kind
== "obj" or kind
== null then
276 if object
.keys
.has
("__id") then
279 if not id
isa Int then
280 errors
.add
new Error("Serialization Error: JSON object declaration declares a non-integer `__id`.")
284 if cache
.has_id
(id
) then
285 errors
.add
new Error("Serialization Error: JSON object with `__id` {id} is deserialized twice.")
290 var class_name
= object
.get_or_null
("__class")
291 if class_name
== null then
292 # Fallback to custom heuristic
293 class_name
= class_name_heuristic
(object
)
296 if class_name
== null then
297 errors
.add
new Error("Serialization Error: JSON object declaration does not declare a `__class`.")
301 if not class_name
isa String then
302 errors
.add
new Error("Serialization Error: JSON object declaration declares a non-string `__class`.")
310 var value
= deserialize_class
(class_name
)
311 just_opened_id
= null
320 if kind
== "char" then
321 if not object
.keys
.has
("__val") then
322 errors
.add
new Error("Serialization Error: JSON `char` object does not declare a `__val`.")
326 var val
= object
["__val"]
328 if not val
isa String or val
.is_empty
then
329 errors
.add
new Error("Serialization Error: JSON `char` object does not declare a single char in `__val`.")
333 return val
.chars
.first
336 errors
.add
new Error("Serialization Error: JSON object has an unknown `__kind`.")
340 # Simple JSON array without serialization metadata
341 if object
isa Array[nullable Object] then
342 var array
= new Array[nullable Object]
343 var types
= new HashSet[String]
344 var has_nullable
= false
346 var res
= convert_object
(e
)
350 types
.add res
.class_name
351 else has_nullable
= true
354 if types
.length
== 1 then
355 var array_type
= types
.first
358 if array_type
== "FlatString" then
360 typed_array
= new Array[nullable FlatString]
361 else typed_array
= new Array[FlatString]
362 else if array_type
== "Int" then
364 typed_array
= new Array[nullable Int]
365 else typed_array
= new Array[Int]
366 else if array_type
== "Float" then
368 typed_array
= new Array[nullable Float]
369 else typed_array
= new Array[Float]
371 # TODO support all array types when we separate the constructor
372 # `from_deserializer` from the filling of the items.
374 if not has_nullable
then
375 typed_array
= new Array[Object]
377 # Unsupported array type, return as `Array[nullable Object]`
382 assert typed_array
isa Array[nullable Object]
384 # Copy item to the new array
385 for e
in array
do typed_array
.add e
389 # Uninferable type, return as `Array[nullable Object]`
396 redef fun deserialize
399 return convert_object
(root
)
402 # User customizable heuristic to get the name of the Nit class to deserialize `json_object`
404 # This method is called only when deserializing an object without the metadata `__class`.
405 # Return the class name as a `String` when it can be inferred.
406 # Return `null` when the class name cannot be found.
408 # If a valid class name is returned, `json_object` will then be deserialized normally.
409 # So it must contain the attributes of the corresponding class, as usual.
424 # class MyJsonDeserializer
425 # super JsonDeserializer
427 # redef fun class_name_heuristic(json_object)
429 # if json_object.keys.has("error") then return "MyError"
430 # if json_object.keys.has("data") then return "MyData"
435 # var json = """{"data": "some other data"}"""
436 # var deserializer = new MyJsonDeserializer(json)
437 # var deserialized = deserializer.deserialize
438 # assert deserialized isa MyData
440 # json = """{"error": "some error message"}"""
441 # deserializer = new MyJsonDeserializer(json)
442 # deserialized = deserializer.deserialize
443 # assert deserialized isa MyError
445 protected fun class_name_heuristic
(json_object
: JsonObject): nullable String
453 # Deserialize a `nullable Object` from this JSON formatted string
455 # Warning: Deserialization errors are reported with `print_error` and
456 # may be returned as a partial object or as `null`.
458 # This method is not appropriate when errors need to be handled programmatically,
459 # manually use a `JsonDeserializer` in such cases.
460 fun from_json_string
: nullable Object
462 var deserializer
= new JsonDeserializer(self)
463 var res
= deserializer
.deserialize
464 if deserializer
.errors
.not_empty
then
465 print_error
"Deserialization Errors: {deserializer.errors.join(", ")}"
471 redef class Serializable
472 private fun serialize_to_json
(v
: JsonSerializer)
474 var id
= v
.cache
.new_id_for
(self)
476 if not v
.plain_json
then
477 v
.stream
.write
"\"__kind\
": \"obj\
", \"__id\
": "
478 v
.stream
.write id
.to_s
479 v
.stream
.write
", \"__class\
": \""
480 v.stream.write class_name
487 # Serialize this object to a JSON string with metadata for deserialization
488 fun to_json_string
: String
490 var stream
= new StringWriter
491 var serializer
= new JsonSerializer(stream
)
492 serializer
.serialize
self
497 # Serialize this object to plain JSON
499 # This is a shortcut using `JsonSerializer::plain_json`,
500 # see its documentation for more information.
501 fun to_plain_json
: String
503 var stream
= new StringWriter
504 var serializer
= new JsonSerializer(stream
)
505 serializer
.plain_json
= true
506 serializer
.serialize
self
513 redef fun serialize_to_json
(v
) do v
.stream
.write
(to_s
)
517 redef fun serialize_to_json
(v
) do v
.stream
.write
(to_s
)
521 redef fun serialize_to_json
(v
) do v
.stream
.write
(to_s
)
525 redef fun serialize_to_json
(v
)
528 v
.stream
.write to_s
.to_json
530 v
.stream
.write
"\{\"__kind\
": \"char\
", \"__val\
": "
531 v
.stream
.write to_s
.to_json
538 redef fun serialize_to_json
(v
) do v
.stream
.write
(to_json
)
541 redef class NativeString
542 redef fun serialize_to_json
(v
) do to_s
.serialize_to_json
(v
)
545 redef class Collection[E
]
546 # Utility to serialize a normal Json array
547 private fun serialize_to_pure_json
(v
: JsonSerializer)
554 else v
.stream
.write
", "
556 if not v
.try_to_serialize
(e
) then
557 v
.warn
("element of type {e.class_name} is not serializable.")
564 redef class SimpleCollection[E
]
565 redef fun serialize_to_json
(v
)
567 # Register as pseudo object
568 if not v
.plain_json
then
569 var id
= v
.cache
.new_id_for
(self)
570 v
.stream
.write
"""{"__kind": "obj", "__id": """
571 v
.stream
.write id
.to_s
572 v
.stream
.write
""", "__class": """"
573 v.stream.write class_name
574 v.stream.write """", "__items": """
577 serialize_to_pure_json v
579 if not v.plain_json then
584 redef init from_deserializer(v)
587 if v isa JsonDeserializer then
588 v.notify_of_creation self
591 var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
593 var obj = v.convert_object(o)
600 redef class Map[K, V]
601 redef fun serialize_to_json(v)
603 # Register as pseudo object
604 var id = v.cache.new_id_for(self)
609 for key, val in self do
614 if key == null then key = "null"
616 v.stream.write key.to_s.to_json
618 if not v.try_to_serialize(val) then
619 v.warn("element of type {val.class_name} is not serializable.")
620 v.stream.write "null"
625 v.stream.write """{"__kind": "obj", "__id": """
626 v.stream.write id.to_s
627 v.stream.write """, "__class": """"
628 v.stream.write class_name
629 v.stream.write """", "__length
": """
630 v.stream.write length.to_s
632 v.stream.write """, "__keys
": """
633 keys.serialize_to_pure_json v
635 v.stream.write """, "__values
": """
636 values.serialize_to_pure_json v
642 redef init from_deserializer(v)
646 if v isa JsonDeserializer then
647 v.notify_of_creation self
650 var length = v.deserialize_attribute("__length
").as(Int)
651 var keys = v.path.last["__keys
"].as(SequenceRead[nullable Object])
652 var values = v.path.last["__values
"].as(SequenceRead[nullable Object])
653 for i in length.times do
654 var key = v.convert_object(keys[i])
655 var value = v.convert_object(values[i])