Merge: Byte literals
[nit.git] / lib / serialization / README.md
1 # Abstract serialization services
2
3 The serialization services are based on the `serialize` and the `noserialize` annotations,
4 the `Serializable` interface and the implementations of `Serializer` and `Deserializer`.
5
6 ## The `serialize` annotation
7
8 A class annotated with `serialize` 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         serialize
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 `serialize`.
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 `serialize`
41 classes or any other subclass of `Serializable`.
42
43 ~~~
44 # This `serialize` class is composed of two `serialize` attributes
45 class Partnership
46         serialize
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 ### Scope of the `serialize` annotation
57
58 `serialize` can annotate class definitions, modules and attributes:
59
60 * The annotation on a class applies only to the class definition,
61   only attributes declared locally will be serialized.
62   However, each definition of a class (a refinement or specialization) can be annotated with `serialize`.
63
64 * A module declaration annotated with `serialize` states that all its class definitions
65   and locally declared attributes are serializable.
66
67   ~~~
68   module shared_between_clients is serialize
69   ~~~
70
71 * Attribute annotated with `serialize` states that it is to be serialized, when the rest of the class does not.
72   The class will become subclass to `Serializable` but its attributes are not to be serialized by default.
73   Only the attributes with the `serialize` annotation will be serialized.
74
75   ~~~
76   # Only serialize the `name`
77   class UserCredentials
78       var name: String is serialize
79       var avatar_path: String = "/somepath/"+name is lazy
80   end
81   ~~~
82
83 ## The `noserialize` annotation
84
85 The `noserialize` annotation mark an exception in a `serialize` module or class definition.
86
87 * By default a module is `noserialize`. There is no need to declare it as such.
88
89 * A class definition annotated with `noserialize` within a `serialize` module will not be made serializable.
90
91 * A `noserialize` attribute within a class or module annotated with `serialize` will not serialize this attribute.
92   The class will still be made subclass of `Serializable` and it won't affect the other attributes.
93   The `noserialize` attribute will not be set at deserialization.
94   Usually, it will also be annotated with `lazy` to get its value by another mean after the object has been deserialized.
95
96   ~~~
97   # Once again, only serialize the `name`
98   class UserCredentials
99       serialize
100
101       var name: String
102       var avatar_path: String = "/somepath/"+name is noserialize, lazy
103   end
104   ~~~
105
106 ## Custom serializable classes
107
108 The annotation `serialize` should be enough for most cases,
109 but in some cases you need more control over the serialization process.
110
111 For more control, create a subclass to `Serializable` and redefine `core_serialize_to`.
112 This method should use `Serializer::serialize_attribute` to serialize its components.
113 `serialize_attribute` works as a dictionary and organize attributes with a key.
114
115 You will also need to redefine `Deserializer::deserialize_class` to support this specific class.
116 The method should only act on known class names, and call super otherwise.
117
118 ### Example: the User class
119
120 The following example cannot use the `serialize` annotations
121 because some of the arguments to the `User` class need special treatment:
122
123 * The `name` attribute is perfectly normal, it can be serialized and deserialized
124   directly.
125
126 * The `password` attribute must be encrypted before being serialized,
127   and unencrypted on deserialization.
128
129 * The `avatar` attributes is kept as ASCII art in memory.
130   It could be serialized as such but it is cleaner to only
131   serialize the path to its source on the file system.
132   The data is reloaded on deserialization.
133
134 For this customization, the following code snippet implements
135 two serialization services: `User::core_serialize_to` and
136 `Deserializer::deserialize_class`.
137
138 ~~~
139 module user_credentials
140
141 # User credentials for a website
142 class User
143         super Serializable
144
145         # User name
146         var name: String
147
148         # Clear text password
149         var password: String
150
151         # User's avatar image as data blob
152         var avatar: Image
153
154         redef fun core_serialize_to(serializer: Serializer)
155         do
156                 # This is the normal serialization process
157                 serializer.serialize_attribute("name", name)
158
159                 # Serialized an encrypted version of the password
160                 #
161                 # Obviously, `rot(13)` is not a good encrption
162                 serializer.serialize_attribute("pass", password.rot(13))
163
164                 # Do not serialize the image, only its path
165                 serializer.serialize_attribute("avatar_path", avatar.path)
166         end
167 end
168
169 redef class Deserializer
170         redef fun deserialize_class(name)
171         do
172                 if name == "User" then
173                         # Deserialize normally
174                         var user = deserialize_attribute("name")
175
176                         # Decrypt password
177                         var pass = deserialize_attribute("pass").rot(-13)
178
179                         # Deserialize the path and load the avatar from the file system
180                         var avatar_path = deserialize_attribute("avatar_path")
181                         var avatar = new Image(avatar_path)
182
183                         return new User(user, pass, avatar)
184                 end
185
186                 return super
187         end
188 end
189
190 # An image loaded in memory as ASCII art
191 #
192 # Not really useful for this example, provided for consistency only.
193 class Image
194         # Path on the filesystem for `self`
195         var path: String
196
197         # ASCII art composing this image
198         var ascii_art: String = path.read_all is lazy
199 end
200
201 ~~~
202
203 See the documentation of the module `serialization::serialization` for more
204 information on the services to redefine.
205
206 ## Serialization services
207
208 The `serialize` annotation and the `Serializable` class are used on
209 classes specific to the business domain.
210 To write (and read) instances of these classes to a persistent format
211 you must use implementations of `Serializer` and `Deserializer`.
212
213 The main implementations of these services are `JsonSerializer` and `JsonDeserializer`,
214 from the `json_serialization` module.
215
216 ~~~
217 import json_serialization
218 import user_credentials
219
220 # Data to be serialized and deserialized
221 var couple = new Partnership(
222         new Person("Alice", 1985, new Image("alice.png")),
223         new Person("Bob", null, new Image("bob.png")))
224
225 var path = "serialized_data.json"
226 var writer = new FileWriter(path)
227 var serializer = new JsonSerializer(writer)
228 serializer.serialize couple
229 writer.close
230
231 var reader = new FileReader(path)
232 var deserializer = new JsonDeserializer(reader.to_s)
233 var deserialized_couple = deserializer.deserialize
234 reader.close
235
236 assert couple == deserialize_couple
237 ~~~
238
239 ## Limitations and TODO
240
241 The serialization has some limitations:
242
243 * Not enough classes from the standard library are supported.
244   This only requires someone to actually code the support.
245   It should not be especially hard for most classes, some can
246   simply declare the `serialize` annotation.
247
248 * A limitation of the Json parser prevents deserializing from files
249   with more than one object.
250   This could be improved in the future, but for now you should
251   serialize a single object to each filesand use different instances of
252   serializer and deserializer each time.
253
254 * The `serialize` annotation does not handle very well
255   complex constructors. This could be improved in the compiler.
256   For now, you may prefer to use `serialize` on simple classes,
257   of by using custom `Serializable`.
258
259 * The serialization uses only the short name of a class, not its qualified name.
260   This will cause problem when different classes using the same name.
261   This could be solved partially in the compiler and the library.
262   A special attention must be given to the consistency of the name across
263   the different programs sharing the serialized data.
264
265 * The serialization support in the compiler need some help to
266   deal with generic types. The solution is to use `nitserial`,
267   the next section explores this subject.
268
269 ## Dealing with generic types
270
271 One limitation of the serialization support in the compiler is with generic types.
272 For example, the `Array` class is generic and serializable.
273 However, the runtime types of Array instances are parameterized and are unknown to the compiler.
274 So the compiler won't support serializing instances of `Array[MySerializable]`.
275
276 The tool `nitserial` solves this problem at the level of user modules.
277 It does so by parsing a Nit module, group or project to find all known
278 parameterized types of generic classes.
279 It will then generating a Nit module to handle deserialization of these types.
280
281 Usage steps to serialize parameterized types:
282
283 * Write your program, let's call it `my_prog.nit`,
284   it must use some parameterized serializable types.
285   Let's say that you use `Array[MySerializable]`.
286
287 * Run nitserial using `nitserial my_prog.nit` to
288   generate the file `my_prog_serial.nit`.
289
290 * Compile your program by mixing in the generated module with:
291   `nitc my_prog.nit -m my_prog_serial.nit`
292
293 This was a simple example, in practical cases you may need
294 to use more than one generated file.
295 For example, on a client/server system, an instance can be created
296 server-side, serialized and the used client-side.
297 In this case, two files will be generated by nitserial,
298 one for the server and one for the client.
299 Both the files should be compiled with both the client and the server.