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