# See the License for the specific language governing permissions and
# limitations under the License.
-# Handles serialization and deserialization of objects to/from Json.
+# Handles serialization and deserialization of objects to/from JSON
+#
+# ## Nity JSON
+#
+# `JsonSerializer` write Nit objects that subclass `Serializable` to JSON,
+# and `JsonDeserializer` can read them. They both use meta-data added to the
+# generated JSON to recreate the Nit instances with the exact original type.
+#
+# For more information on Nit serialization, see: ../serialization/README.md
+#
+# ## Plain JSON
+#
+# The attribute `JsonSerializer::plain_json` triggers generating plain and
+# clean JSON. This format is easier to read for an human and a non-Nit program,
+# but it cannot be fully deserialized. It can still be read by services from
+# `json::static` and `json::dynamic`.
+#
+# A shortcut to this service is provided by `Serializable::to_plain_json`.
+#
+# ### Usage Example
+#
+# ~~~nitish
+# import json::serialization
+#
+# class Person
+# serialize
+#
+# var name: String
+# var year_of_birth: Int
+# var next_of_kin: nullable Person
+# end
+#
+# var bob = new Person("Bob", 1986)
+# var alice = new Person("Alice", 1978, bob)
+#
+# assert bob.to_plain_json == """
+# {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}"""
+#
+# assert alice.to_plain_json == """
+# {"name": "Alice", "year_of_birth": 1978, "next_of_kin": {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}}"""
+# ~~~
module serialization
-import ::serialization
+import ::serialization::caching
private import ::serialization::engine_tools
-import static
+private import static
# Serializer of Nit objects to Json string.
class JsonSerializer
- super Serializer
+ super CachingSerializer
# Target writing stream
var stream: Writer
# * Does not support cycles, will replace the problematic references by `null`.
# * Does not serialize the meta-data needed to deserialize the objects
# back to regular Nit objects.
+ # * Keys of Nit `HashMap` are converted to their string reprensentation using `to_s`.
var plain_json = false is writable
# List of the current open objects, the first is the main target of the serialization
redef fun serialize_reference(object)
do
- if not plain_json and refs_map.has_key(object) then
+ if not plain_json and cache.has_object(object) then
# if already serialized, add local reference
- var id = ref_id_for(object)
+ var id = cache.id_for(object)
stream.write "\{\"__kind\": \"ref\", \"__id\": "
stream.write id.to_s
stream.write "\}"
serialize object
end
end
-
- # Map of references to already serialized objects.
- private var refs_map = new StrictHashMap[Serializable,Int]
-
- # Get the internal serialized reference for this `object`.
- private fun ref_id_for(object: Serializable): Int
- do
- if refs_map.has_key(object) then
- return refs_map[object]
- else
- var id = refs_map.length
- refs_map[object] = id
- return id
- end
- end
end
# Deserializer from a Json string.
class JsonDeserializer
- super Deserializer
+ super CachingDeserializer
# Json text to deserialize from.
private var text: Text
# Root json object parsed from input text.
- var root: nullable Jsonable is noinit
+ private var root: nullable Jsonable is noinit
# Depth-first path in the serialized object tree.
- var path = new Array[JsonObject]
-
- # Map of references to already deserialized objects.
- private var id_to_object = new StrictHashMap[Int, Object]
+ private var path = new Array[JsonObject]
# Last encountered object reference id.
#
do
var id = just_opened_id
if id == null then return # Register `new_object` only once
- id_to_object[id] = new_object
+ cache[id] = new_object
end
# Convert from simple Json object to Nit object
var id = object["__id"]
assert id isa Int
- assert id_to_object.has_key(id)
- return id_to_object[id]
+ assert cache.has_id(id)
+ return cache.object_for(id)
end
# obj?
var class_name = object["__class"]
assert class_name isa String
- assert not id_to_object.has_key(id) else print "Error: Object with id '{id}' of {class_name} is deserialized twice."
+ assert not cache.has_id(id) else print "Error: Object with id '{id}' of {class_name} is deserialized twice."
# advance on path
path.push object
redef class Serializable
private fun serialize_to_json(v: JsonSerializer)
do
- var id = v.ref_id_for(self)
+ var id = v.cache.new_id_for(self)
v.stream.write "\{"
if not v.plain_json then
v.stream.write "\"__kind\": \"obj\", \"__id\": "
core_serialize_to(v)
v.stream.write "\}"
end
+
+ # Serialize this object to plain JSON
+ #
+ # This is a shortcut using `JsonSerializer::plain_json`,
+ # see its documentation for more information.
+ fun to_plain_json: String
+ do
+ var stream = new StringWriter
+ var serializer = new JsonSerializer(stream)
+ serializer.plain_json = true
+ serializer.serialize self
+ stream.close
+ return stream.to_s
+ end
end
redef class Int
redef fun serialize_to_json(v)
do
# Register as pseudo object
- var id = v.ref_id_for(self)
- v.stream.write """{"__kind": "obj", "__id": """
- v.stream.write id.to_s
- v.stream.write """, "__class": """"
- v.stream.write class_name
- v.stream.write """", "__length": """
- v.stream.write length.to_s
- v.stream.write """, "__items": """
+ if not v.plain_json then
+ var id = v.cache.new_id_for(self)
+ v.stream.write """{"__kind": "obj", "__id": """
+ v.stream.write id.to_s
+ v.stream.write """, "__class": """"
+ v.stream.write class_name
+ v.stream.write """", "__length": """
+ v.stream.write length.to_s
+ v.stream.write """, "__items": """
+ end
+
serialize_to_pure_json v
- v.stream.write "\}"
+
+ if not v.plain_json then
+ v.stream.write "\}"
+ end
end
redef init from_deserializer(v: Deserializer)
do
+ super
if v isa JsonDeserializer then
v.notify_of_creation self
init
redef fun serialize_to_json(v)
do
# Register as pseudo object
- var id = v.ref_id_for(self)
+ var id = v.cache.new_id_for(self)
if v.plain_json then
v.stream.write "\{"
- var first = false
+ var first = true
for key, val in self do
if not first then
v.stream.write ", "
- else first = true
+ else first = false
if key == null then key = "null"
# Instantiate a new `Array` from its serialized representation.
redef init from_deserializer(v: Deserializer)
do
- init
+ super
if v isa JsonDeserializer then
v.notify_of_creation self
+ init
var length = v.deserialize_attribute("__length").as(Int)
var keys = v.path.last["__keys"].as(SequenceRead[nullable Object])