lib/serialization: add README.md
[nit.git] / lib / serialization / README.md
1 # Abstract serialization services
2
3 The serialization services are centered around the `auto_serializable` annotation,
4 the `Serializable` interface and the implementations of `Serializer` and `Deserializer`.
5
6 ## The `auto_serializable` annotation
7
8 A class annotated with `auto_serializable` identifies it as a subclass of Serializable and
9 triggers the generation of customized serialization and deserialization services.
10
11 ~~~
12 import serialization
13
14 # Simple serializable class identifying a human
15 class Person
16         auto_serializable
17
18         # First and last name
19         var name: String
20
21         # Year of birth (`null` if unknown)
22         var birth: nullable Int
23
24         redef fun ==(o) do return o isa SELF and name == o.name and birth == o.birth
25         redef fun hash do return name.hash
26 end
27 ~~~
28
29 The `Person` class also defines `==` and `hash`, this is optional but we will use it to make an important point.
30 By definition of a serializable class, an instance can be serialized to a stream, then deserialized.
31 The deserialized instance will not be the same instance, but they should be equal.
32 So, in this case, we can compare both instances with `==` to test their equality.
33
34 Some conditions applies to the classes that can be annotated as `auto_serializable`.
35 All attributes of the class must be serializable, runtime errors will be
36 raised when trying to serialize non-serializable attributes.
37
38 In the class `Person`, all attributes are typed with classes the standards library.
39 These common types are defined defined as serializable by this project.
40 The attributes could also be typed with user-defined `auto_serializable`
41 classes or any other subclass of `Serializable`.
42
43 ~~~
44 # This `auto_serializable` class is composed of two `auto_serializable` attributes
45 class Partnership
46         auto_serializable
47
48         var partner_a: Person
49         var partner_b: Person
50
51         redef fun ==(o) do return o isa SELF and partner_a == o.partner_a and partner_b == o.partner_b
52         redef fun hash do return partner_a.hash + 1024*partner_b.hash
53 end
54 ~~~
55
56 The `auto_serializable` applies only to the class definition,
57 only attributes declared locally will be serialized.
58 However, each definition of a class (a refinement or specialization)
59 can declare `auto_serializable`.
60
61 ## Custom serializable classes
62
63 The annotation `auto_serializable` should be enough for most cases,
64 but in some cases you need more control over the serialization process.
65
66 For more control, create a subclass to `Serializable` and redefine `core_serialize_to`.
67 This method should use `Serializer::serialize_attribute` to serialize its components.
68 `serialize_attribute` works as a dictionary and organize attributes with a key.
69
70 You will also need to redefine `Deserializer::deserialize_class` to support this specific class.
71 The method should only act on known class names, and call super otherwise.
72
73 ### Example: the User class
74
75 The following example cannot use the `auto_serializable` annotations
76 because some of the arguments to the `User` class need special treatment:
77
78 * The `name` attribute is perfectly normal, it can be serialized and deserialized
79   directly.
80
81 * The `password` attribute must be encrypted before being serialized,
82   and unencrypted on deserialization.
83
84 * The `avatar` attributes is kept as ASCII art in memory.
85   It could be serialized as such but it is cleaner to only
86   serialize the path to its source on the file system.
87   The data is reloaded on deserialization.
88
89 For this customization, the following code snippet implements
90 two serialization services: `User::core_serialize_to` and
91 `Deserializer::deserialize_class`.
92
93 ~~~
94 module user_credentials
95
96 # User credentials for a website
97 class User
98         super Serializable
99
100         # User name
101         var name: String
102
103         # Clear text password
104         var password: String
105
106         # User's avatar image as data blob
107         var avatar: Image
108
109         redef fun core_serialize_to(serializer: Serializer)
110         do
111                 # This is the normal serialization process
112                 serializer.serialize_attribute("name", name)
113
114                 # Serialized an encrypted version of the password
115                 #
116                 # Obviously, `rot(13)` is not a good encrption
117                 serializer.serialize_attribute("pass", password.rot(13))
118
119                 # Do not serialize the image, only its path
120                 serializer.serialize_attribute("avatar_path", avatar.path)
121         end
122 end
123
124 redef class Deserializer
125         redef fun deserialize_class(name)
126         do
127                 if name == "User" then
128                         # Deserialize normally
129                         var user = deserialize_attribute("name")
130
131                         # Decrypt password
132                         var pass = deserialize_attribute("pass").rot(-13)
133
134                         # Deserialize the path and load the avatar from the file system
135                         var avatar_path = deserialize_attribute("avatar_path")
136                         var avatar = new Image(avatar_path)
137
138                         return new User(user, pass, avatar)
139                 end
140
141                 return super
142         end
143 end
144
145 # An image loaded in memory as ASCII art
146 #
147 # Not really useful for this example, provided for consistency only.
148 class Image
149         # Path on the filesystem for `self`
150         var path: String
151
152         # ASCII art composing this image
153         var ascii_art: String = path.read_all is lazy
154 end
155
156 ~~~
157
158 See the documentation of the module `serialization::serialization` for more
159 information on the services to redefine.
160
161 ## Serialization services
162
163 The `auto_serializable` annotation and the `Serializable` class are used on
164 classes specific to the business domain.
165 To write (and read) instances of these classes to a persistent format
166 you must use implementations of `Serializer` and `Deserializer`.
167
168 The main implementations of these services are `JsonSerializer` and `JsonDeserializer`,
169 from the `json_serialization` module.
170
171 ~~~
172 import json_serialization
173 import user_credentials
174
175 # Data to be serialized and deserialized
176 var couple = new Partnership(
177         new Person("Alice", 1985, new Image("alice.png")),
178         new Person("Bob", null, new Image("bob.png")))
179
180 var path = "serialized_data.json"
181 var writer = new FileWriter(path)
182 var serializer = new JsonSerializer(writer)
183 serializer.serialize couple
184 writer.close
185
186 var reader = new FileReader(path)
187 var deserializer = new JsonDeserializer(reader.to_s)
188 var deserialized_couple = deserializer.deserialize
189 reader.close
190
191 assert couple == deserialize_couple
192 ~~~
193
194 ## Limitations and TODO
195
196 The serialization has some limitations:
197
198 * Not enough classes from the standard library are supported.
199   This only requires someone to actually code the support.
200   It should not be especially hard for most classes, some can
201   simply declare the `auto_serializable` annotation.
202
203 * A limitation of the Json parser prevents deserializing from files
204   with more than one object.
205   This could be improved in the future, but for now you should
206   serialize a single object to each filesand use different instances of
207   serializer and deserializer each time.
208
209 * The `auto_serializable` annotation does not handle very well
210   complex constructors. This could be improved in the compiler.
211   For now, you may prefer to use `auto_serializable` on simple classes,
212   of by using custom `Serializable`.
213
214 * The serialization uses only the short name of a class, not its qualified name.
215   This will cause problem when different classes using the same name.
216   This could be solved partially in the compiler and the library.
217   A special attention must be given to the consistency of the name across
218   the different programs sharing the serialized data.
219
220 * The serialization support in the compiler need some help to
221   deal with generic types. The solution is to use `nitserial`,
222   the next section explores this subject.
223
224 ## Dealing with generic types
225
226 One limitation of the serialization support in the compiler is with generic types.
227 For example, the `Array` class is generic and serializable.
228 However, the runtime types of Array instances are parameterized and are unknown to the compiler.
229 So the compiler won't support serializing instances of `Array[MySerializable]`.
230
231 The tool `nitserial` solves this problem at the level of user modules.
232 It does so by parsing a Nit module, group or project to find all known
233 parameterized types of generic classes.
234 It will then generating a Nit module to handle deserialization of these types.
235
236 Usage steps to serialize parameterized types:
237
238 * Write your program, let's call it `my_prog.nit`,
239   it must use some parameterized serializable types.
240   Let's say that you use `Array[MySerializable]`.
241
242 * Run nitserial using `nitserial my_prog.nit` to
243   generate the file `my_prog_serial.nit`.
244
245 * Compile your program by mixing in the generated module with:
246   `nitc my_prog.nit -m my_prog_serial.nit`
247
248 This was a simple example, in practical cases you may need
249 to use more than one generated file.
250 For example, on a client/server system, an instance can be created
251 server-side, serialized and the used client-side.
252 In this case, two files will be generated by nitserial,
253 one for the server and one for the client.
254 Both the files should be compiled with both the client and the server.