The Nit serialization system did not have any documentation. This is the base to a better documentation.
In a next PR I may make more services of the serializers private. Implementations, such as json_serialization, will need to intrude import serialization. However, the end-user will only see the basic services.
Pull-Request: #1202
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Philippe Pépos Petitclerc <>
Reviewed-by: Frédéric Vachon <fredvac@gmail.com>
assert current.keys.has(name)
var value = current[name]
-
+
return convert_object(value)
end
assert val isa String
if val.length != 1 then print "Error: expected a single char when deserializing '{val}'."
-
+
return val.chars.first
end
+++ /dev/null
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Offers services to serialize a Nit objects to different persistent formats
-module serialization is
- new_annotation auto_serializable
-end
-
-# Abstract serialization service to be sub-classed by specialized services.
-interface Serializer
- # Main method of this service, serialize the `object`
- fun serialize(object: nullable Serializable) is abstract
-
- # Serialize an object as a "possible" reference, depending of the service
- fun serialize_reference(object: Serializable) is abstract
-
- # Serialize an attribute, used by `Serializable::core_serialize_to`
- fun serialize_attribute(name: String, value: nullable Object)
- do
- if not try_to_serialize(value) then
- warn("argument {value.class_name}::{name} is not serializable.")
- end
- end
-
- # Serialize `value` is possie, i.e. it is `Serializable` or `null`
- fun try_to_serialize(value: nullable Object): Bool
- do
- if value isa Serializable then
- value.serialize_to_or_delay(self)
- else if value == null then
- serialize value
- else return false
- return true
- end
-
- # Warn of problems and potential errors (such as if an attribute
- # is not serializable)
- fun warn(msg: String) do print "Serialization warning: {msg}"
-end
-
-# Abstract deserialization service
-#
-# After initialization of one of its sub-classes, call `deserialize`
-interface Deserializer
- # Main method of this class, returns a Nit object
- fun deserialize: nullable Object is abstract
-
- # Internal method to be implemented by sub-classes
- fun deserialize_attribute(name: String): nullable Object is abstract
-
- # Internal method called by objects in creation,
- # to be implemented by sub-classes
- fun notify_of_creation(new_object: Object) is abstract
-
- # Mainly generated method to return the next instance of the givent
- # class by name
- fun deserialize_class(class_name: String): Object do
- print "Error: doesn't know how to deserialize class \"{class_name}\""
- abort
- end
-end
-
-# Instances of this class can be passed to `Serializer::serialize`
-interface Serializable
- # Full or true serialization
- fun serialize_to(v: Serializer) do v.serialize(self)
-
- # Body of the serialization of this class
- # Can be redefed in sub classes and refinements
- fun core_serialize_to(v: Serializer) do end
-
- # Whether full serialization (calls `serialize_to`) or place only references
- fun serialize_to_or_delay(v: Serializer) do v.serialize_reference(self)
-end
-
-# Instances of this class are not delayed and instead serialized immediately
-# This applies mainly to `universal` types
-interface DirectSerializable
- super Serializable
-
- redef fun serialize_to_or_delay(v) do serialize_to(v)
-end
-
-redef class Bool super DirectSerializable end
-redef class Char super DirectSerializable end
-redef class Int super DirectSerializable end
-redef class Float super DirectSerializable end
-redef class NativeString super DirectSerializable end
-redef class String super DirectSerializable end
-redef class Array[E] super Serializable end
--- /dev/null
+# Abstract serialization services
+
+The serialization services are centered around the `auto_serializable` annotation,
+the `Serializable` interface and the implementations of `Serializer` and `Deserializer`.
+
+## The `auto_serializable` annotation
+
+A class annotated with `auto_serializable` identifies it as a subclass of Serializable and
+triggers the generation of customized serialization and deserialization services.
+
+~~~
+import serialization
+
+# Simple serializable class identifying a human
+class Person
+ auto_serializable
+
+ # First and last name
+ var name: String
+
+ # Year of birth (`null` if unknown)
+ var birth: nullable Int
+
+ redef fun ==(o) do return o isa SELF and name == o.name and birth == o.birth
+ redef fun hash do return name.hash
+end
+~~~
+
+The `Person` class also defines `==` and `hash`, this is optional but we will use it to make an important point.
+By definition of a serializable class, an instance can be serialized to a stream, then deserialized.
+The deserialized instance will not be the same instance, but they should be equal.
+So, in this case, we can compare both instances with `==` to test their equality.
+
+Some conditions applies to the classes that can be annotated as `auto_serializable`.
+All attributes of the class must be serializable, runtime errors will be
+raised when trying to serialize non-serializable attributes.
+
+In the class `Person`, all attributes are typed with classes the standards library.
+These common types are defined defined as serializable by this project.
+The attributes could also be typed with user-defined `auto_serializable`
+classes or any other subclass of `Serializable`.
+
+~~~
+# This `auto_serializable` class is composed of two `auto_serializable` attributes
+class Partnership
+ auto_serializable
+
+ var partner_a: Person
+ var partner_b: Person
+
+ redef fun ==(o) do return o isa SELF and partner_a == o.partner_a and partner_b == o.partner_b
+ redef fun hash do return partner_a.hash + 1024*partner_b.hash
+end
+~~~
+
+The `auto_serializable` applies only to the class definition,
+only attributes declared locally will be serialized.
+However, each definition of a class (a refinement or specialization)
+can declare `auto_serializable`.
+
+## Custom serializable classes
+
+The annotation `auto_serializable` should be enough for most cases,
+but in some cases you need more control over the serialization process.
+
+For more control, create a subclass to `Serializable` and redefine `core_serialize_to`.
+This method should use `Serializer::serialize_attribute` to serialize its components.
+`serialize_attribute` works as a dictionary and organize attributes with a key.
+
+You will also need to redefine `Deserializer::deserialize_class` to support this specific class.
+The method should only act on known class names, and call super otherwise.
+
+### Example: the User class
+
+The following example cannot use the `auto_serializable` annotations
+because some of the arguments to the `User` class need special treatment:
+
+* The `name` attribute is perfectly normal, it can be serialized and deserialized
+ directly.
+
+* The `password` attribute must be encrypted before being serialized,
+ and unencrypted on deserialization.
+
+* The `avatar` attributes is kept as ASCII art in memory.
+ It could be serialized as such but it is cleaner to only
+ serialize the path to its source on the file system.
+ The data is reloaded on deserialization.
+
+For this customization, the following code snippet implements
+two serialization services: `User::core_serialize_to` and
+`Deserializer::deserialize_class`.
+
+~~~
+module user_credentials
+
+# User credentials for a website
+class User
+ super Serializable
+
+ # User name
+ var name: String
+
+ # Clear text password
+ var password: String
+
+ # User's avatar image as data blob
+ var avatar: Image
+
+ redef fun core_serialize_to(serializer: Serializer)
+ do
+ # This is the normal serialization process
+ serializer.serialize_attribute("name", name)
+
+ # Serialized an encrypted version of the password
+ #
+ # Obviously, `rot(13)` is not a good encrption
+ serializer.serialize_attribute("pass", password.rot(13))
+
+ # Do not serialize the image, only its path
+ serializer.serialize_attribute("avatar_path", avatar.path)
+ end
+end
+
+redef class Deserializer
+ redef fun deserialize_class(name)
+ do
+ if name == "User" then
+ # Deserialize normally
+ var user = deserialize_attribute("name")
+
+ # Decrypt password
+ var pass = deserialize_attribute("pass").rot(-13)
+
+ # Deserialize the path and load the avatar from the file system
+ var avatar_path = deserialize_attribute("avatar_path")
+ var avatar = new Image(avatar_path)
+
+ return new User(user, pass, avatar)
+ end
+
+ return super
+ end
+end
+
+# An image loaded in memory as ASCII art
+#
+# Not really useful for this example, provided for consistency only.
+class Image
+ # Path on the filesystem for `self`
+ var path: String
+
+ # ASCII art composing this image
+ var ascii_art: String = path.read_all is lazy
+end
+
+~~~
+
+See the documentation of the module `serialization::serialization` for more
+information on the services to redefine.
+
+## Serialization services
+
+The `auto_serializable` annotation and the `Serializable` class are used on
+classes specific to the business domain.
+To write (and read) instances of these classes to a persistent format
+you must use implementations of `Serializer` and `Deserializer`.
+
+The main implementations of these services are `JsonSerializer` and `JsonDeserializer`,
+from the `json_serialization` module.
+
+~~~
+import json_serialization
+import user_credentials
+
+# Data to be serialized and deserialized
+var couple = new Partnership(
+ new Person("Alice", 1985, new Image("alice.png")),
+ new Person("Bob", null, new Image("bob.png")))
+
+var path = "serialized_data.json"
+var writer = new FileWriter(path)
+var serializer = new JsonSerializer(writer)
+serializer.serialize couple
+writer.close
+
+var reader = new FileReader(path)
+var deserializer = new JsonDeserializer(reader.to_s)
+var deserialized_couple = deserializer.deserialize
+reader.close
+
+assert couple == deserialize_couple
+~~~
+
+## Limitations and TODO
+
+The serialization has some limitations:
+
+* Not enough classes from the standard library are supported.
+ This only requires someone to actually code the support.
+ It should not be especially hard for most classes, some can
+ simply declare the `auto_serializable` annotation.
+
+* A limitation of the Json parser prevents deserializing from files
+ with more than one object.
+ This could be improved in the future, but for now you should
+ serialize a single object to each filesand use different instances of
+ serializer and deserializer each time.
+
+* The `auto_serializable` annotation does not handle very well
+ complex constructors. This could be improved in the compiler.
+ For now, you may prefer to use `auto_serializable` on simple classes,
+ of by using custom `Serializable`.
+
+* The serialization uses only the short name of a class, not its qualified name.
+ This will cause problem when different classes using the same name.
+ This could be solved partially in the compiler and the library.
+ A special attention must be given to the consistency of the name across
+ the different programs sharing the serialized data.
+
+* The serialization support in the compiler need some help to
+ deal with generic types. The solution is to use `nitserial`,
+ the next section explores this subject.
+
+## Dealing with generic types
+
+One limitation of the serialization support in the compiler is with generic types.
+For example, the `Array` class is generic and serializable.
+However, the runtime types of Array instances are parameterized and are unknown to the compiler.
+So the compiler won't support serializing instances of `Array[MySerializable]`.
+
+The tool `nitserial` solves this problem at the level of user modules.
+It does so by parsing a Nit module, group or project to find all known
+parameterized types of generic classes.
+It will then generating a Nit module to handle deserialization of these types.
+
+Usage steps to serialize parameterized types:
+
+* Write your program, let's call it `my_prog.nit`,
+ it must use some parameterized serializable types.
+ Let's say that you use `Array[MySerializable]`.
+
+* Run nitserial using `nitserial my_prog.nit` to
+ generate the file `my_prog_serial.nit`.
+
+* Compile your program by mixing in the generated module with:
+ `nitc my_prog.nit -m my_prog_serial.nit`
+
+This was a simple example, in practical cases you may need
+to use more than one generated file.
+For example, on a client/server system, an instance can be created
+server-side, serialized and the used client-side.
+In this case, two files will be generated by nitserial,
+one for the server and one for the client.
+Both the files should be compiled with both the client and the server.
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Abstract services to serialize Nit objects to different formats
+#
+# This module declares the `auto_serializable` annotation to mark Nit classes as serializable.
+# For an introduction to this service, refer to the documentation of the `serialization` group.
+# This documentation provides more technical information on interesting entitie of this module.
+#
+# Interesting entities for end users of serializable classes:
+#
+# * Serialize an instance subclass of `Serializable` with either
+# `Serializer::serializable` and `Serializable::serialize`.
+# * Deserialize an object using `Deserializer::deserialize`.
+# The object type must the be checked with an `assert` or otherwise.
+#
+# Interesting entities to create custom serializable classes:
+#
+# * Subclass `Serializable` to declare a class as serializable and to customize
+# the serialization and deserialization behavior.
+# * Redefine `Serializable::core_serialize_to` to customize the serialization
+# of the receiver class.
+# * Redefine `Deserializer::deserialize_class` to customize the deserialization
+# of a specific class by name.
+#
+# Interesting entities for serialization format:
+#
+# * Subclass `Serializer` and `Deserializer` with custom serices.
+# * In `Serializer`, `serialize` and `serialize_reference` must be redefined.
+# * In `Deserializer`; `deserialize`, `deserialize_attribute and
+# `notify_of_creation` must be redefined.
+module serialization is
+ new_annotation auto_serializable
+end
+
+# Abstract serialization service to be sub-classed by specialized services.
+interface Serializer
+ # Entry point method of this service, serialize the `object`
+ #
+ # This method, and refinements, should handle `null` and probably
+ # use double dispatch to customize the bahavior per serializable objects.
+ fun serialize(object: nullable Serializable) is abstract
+
+ # Serialize an object, with full serialization or a simple reference
+ protected fun serialize_reference(object: Serializable) is abstract
+
+ # Serialize an attribute to compose a serializable object
+ #
+ # This method should be called from `Serializable::core_serialize_to`.
+ fun serialize_attribute(name: String, value: nullable Object)
+ do
+ if not try_to_serialize(value) then
+ warn("argument {value.class_name}::{name} is not serializable.")
+ end
+ end
+
+ # Serialize `value` is possie, i.e. it is `Serializable` or `null`
+ fun try_to_serialize(value: nullable Object): Bool
+ do
+ if value isa Serializable then
+ value.serialize_to_or_delay(self)
+ else if value == null then
+ serialize value
+ else return false
+ return true
+ end
+
+ # Warn of problems and potential errors (such as if an attribute
+ # is not serializable)
+ fun warn(msg: String) do print "Serialization warning: {msg}"
+end
+
+# Abstract deserialization service
+#
+# After initialization of one of its sub-classes, call `deserialize`
+interface Deserializer
+ # Main method of this class, returns a Nit object
+ fun deserialize: nullable Object is abstract
+
+ # Internal method to be implemented by sub-classes
+ fun deserialize_attribute(name: String): nullable Object is abstract
+
+ # Internal method called by objects in creation,
+ # to be implemented by sub-classes
+ fun notify_of_creation(new_object: Object) is abstract
+
+ # Deserialize the next available object as an instance of `class_name`
+ #
+ # Returns the deserialized object on success, aborts on error.
+ #
+ # This method should be redefined for each custom subclass of `Serializable`.
+ # All refinement should look for a precise `class_name` and call super
+ # on unsupported classes.
+ fun deserialize_class(class_name: String): Object do
+ print "Error: doesn't know how to deserialize class \"{class_name}\""
+ abort
+ end
+end
+
+# Instances of this class can be passed to `Serializer::serialize`
+interface Serializable
+ # Serialize `self` to `serializer`
+ #
+ # This is a shortcut to `Serializer::serialize`.
+ fun serialize_to(serializer: Serializer) do serializer.serialize(self)
+
+ # Actual serialization of `self` to `serializer`
+ #
+ # This writes the full data of `self` to `serializer`.
+ #
+ # This method can be redefined in sub classes and refinements.
+ # It should use `Serializer::serialize_attribute` to to register real or
+ # logical attributes.
+ #
+ # Any refinement should have its equivalent refinement of
+ # `Deserializer::deserialize_class` to support this custom deserialization.
+ fun core_serialize_to(serializer: Serializer) do end
+
+ # Accept references or force direct serialization (using `serialize_to`)
+ #
+ # The subclass change the default behavior, which will accept references,
+ # to force to always serialize copies of `self`.
+ private fun serialize_to_or_delay(v: Serializer) do v.serialize_reference(self)
+end
+
+# Instances of this class are not delayed and instead serialized immediately
+# This applies mainly to `universal` types
+interface DirectSerializable
+ super Serializable
+
+ redef fun serialize_to_or_delay(v) do serialize_to(v)
+end
+
+redef class Bool super DirectSerializable end
+redef class Char super DirectSerializable end
+redef class Int super DirectSerializable end
+redef class Float super DirectSerializable end
+redef class NativeString super DirectSerializable end
+redef class String super DirectSerializable end
+redef class Array[E] super Serializable end
-Runtime error: Aborted (../lib/serialization.nit:72)
+Runtime error: Aborted (../lib/serialization/serialization.nit:109)
# Nit:
<A: true a 0.123 1234 asdf false>
-Runtime error: Aborted (../lib/serialization.nit:72)
+Runtime error: Aborted (../lib/serialization/serialization.nit:109)
# Nit:
<A: true a 0.123 1234 asdf false>
-Runtime error: Aborted (../lib/serialization.nit:72)
+Runtime error: Aborted (../lib/serialization/serialization.nit:109)
# Nit:
<A: true a 0.123 1234 asdf false>