examples: annotate examples
[nit.git] / lib / json / serialization_write.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Services to write Nit objects to JSON strings: `serialize_to_json` and `JsonSerializer`
16 module serialization_write
17
18 import ::serialization::caching
19 private import ::serialization::engine_tools
20
21 # Serializer of Nit objects to Json string.
22 class JsonSerializer
23 super CachingSerializer
24
25 # Target writing stream
26 var stream: Writer
27
28 # Write plain JSON? Standard JSON without metadata for deserialization
29 #
30 # If `false`, the default, serialize to support deserialization:
31 #
32 # * Write metadata, including the types of the serialized objects so they can
33 # be deserialized to their original form using `JsonDeserializer`.
34 # * Use references when an object has already been serialized so to not duplicate it.
35 # * Support cycles in references.
36 # * Preserve the Nit `Char` and `Byte` types as special objects.
37 # * The generated JSON is standard and can be read by non-Nit programs.
38 # However, some Nit types are not represented by the simplest possible JSON representation.
39 # With the added metadata, it can be complex to read.
40 #
41 # If `true`, serialize for other programs:
42 #
43 # * Nit objects are serialized to pure and standard JSON so they can
44 # be easily read by non-Nit programs and humans.
45 # * Nit objects are serialized for every references, so they can be duplicated.
46 # It is easier to read but it creates a larger output.
47 # * Does not support cycles, will replace the problematic references by `null`.
48 # * Does not serialize the metadata needed to deserialize the objects
49 # back to regular Nit objects.
50 # * Keys of Nit `HashMap` are converted to their string representation using `to_s`.
51 var plain_json = false is writable
52
53 # Write pretty JSON for human eyes?
54 #
55 # Toggles skipping lines between attributes of an object and
56 # properly indent the written JSON.
57 var pretty_json = false is writable
58
59 # Current indentation level used for writing `pretty_json`
60 private var indent_level = 0
61
62 # List of the current open objects, the first is the main target of the serialization
63 #
64 # Used only when `plain_json == true` to detect cycles in serialization.
65 private var open_objects = new Array[Object]
66
67 # Has the first attribute of the current object already been serialized?
68 #
69 # Used only when `plain_json == true`.
70 private var first_attribute = false
71
72 redef var current_object = null
73
74 redef fun serialize(object)
75 do
76 if object == null then
77 stream.write "null"
78 else
79 if plain_json then
80 for o in open_objects do
81 if object.is_same_serialized(o) then
82 # Cycle, can't be managed in plain json
83 warn "Cycle detected in serialized object, replacing reference with 'null'."
84 stream.write "null"
85 return
86 end
87 end
88
89 open_objects.add object
90 end
91
92 first_attribute = true
93 var last_object = current_object
94 current_object = object
95 object.accept_json_serializer self
96 first_attribute = false
97 current_object = last_object
98
99 if plain_json then open_objects.pop
100 end
101 end
102
103 redef fun serialize_attribute(name, value)
104 do
105 if not plain_json or not first_attribute then
106 stream.write ","
107 end
108 first_attribute = false
109
110 new_line_and_indent
111 stream.write "\""
112 stream.write name
113 stream.write "\":"
114 if pretty_json then stream.write " "
115 super
116 end
117
118 redef fun serialize_reference(object)
119 do
120 if not plain_json and cache.has_object(object) then
121 # if already serialized, add local reference
122 var id = cache.id_for(object)
123 stream.write "\{"
124 indent_level += 1
125 new_line_and_indent
126 stream.write "\"__kind\": \"ref\", \"__id\": "
127 stream.write id.to_s
128 indent_level -= 1
129 new_line_and_indent
130 stream.write "\}"
131 else
132 # serialize here
133 serialize object
134 end
135 end
136
137 # Write a new line and indent it, only if `pretty_json`
138 private fun new_line_and_indent
139 do
140 if pretty_json then
141 stream.write "\n"
142 for i in indent_level.times do stream.write "\t"
143 end
144 end
145 end
146
147 redef class Text
148
149 redef fun accept_json_serializer(v)
150 do
151 v.stream.write "\""
152
153 var start_i = 0
154 var escaped = null
155 for i in [0 .. self.length[ do
156 var char = self[i]
157 if char == '\\' then
158 escaped = "\\\\"
159 else if char == '\"' then
160 escaped = "\\\""
161 else if char < ' ' then
162 if char == '\n' then
163 escaped = "\\n"
164 else if char == '\r' then
165 escaped = "\\r"
166 else if char == '\t' then
167 escaped = "\\t"
168 else
169 escaped = char.escape_to_utf16
170 end
171 end
172
173 if escaped != null then
174 # Write open non-escaped string
175 if start_i <= i then
176 v.stream.write substring(start_i, i-start_i)
177 end
178
179 # Write escaped character
180 v.stream.write escaped
181 escaped = null
182 start_i = i+1
183 end
184 end
185
186 # Write remaining non-escaped string
187 if start_i < length then
188 if start_i == 0 then
189 v.stream.write self
190 else
191 v.stream.write substring(start_i, length-start_i)
192 end
193 end
194
195 v.stream.write "\""
196 end
197 end
198
199 redef class Serializable
200
201 # Serialize `self` to JSON
202 #
203 # Set `plain = true` to generate standard JSON, without deserialization metadata.
204 # Use this option if the generated JSON will be read by other programs or humans.
205 # Use the default, `plain = false`, if the JSON is to be deserialized by a Nit program.
206 #
207 # Set `pretty = true` to generate pretty JSON for human eyes.
208 # Use the default, `pretty = false`, to generate minified JSON.
209 #
210 # This method should not be refined by subclasses,
211 # instead `accept_json_serializer` can customize the serialization of an object.
212 #
213 # See: `JsonSerializer`
214 fun serialize_to_json(plain, pretty: nullable Bool): String
215 do
216 var stream = new StringWriter
217 var serializer = new JsonSerializer(stream)
218 serializer.plain_json = plain or else false
219 serializer.pretty_json = pretty or else false
220 serializer.serialize self
221 stream.close
222 return stream.to_s
223 end
224
225 # Serialize `self` to plain JSON
226 #
227 # Compatibility alias for `serialize_to_json(plain=true)`.
228 fun to_json: String do return serialize_to_json(plain=true)
229
230 # Serialize `self` to plain pretty JSON
231 #
232 # Compatibility alias for `serialize_to_json(plain=true, pretty=true)`.
233 fun to_pretty_json: String do return serialize_to_json(plain=true, pretty=true)
234
235 # Refinable service to customize the serialization of this class to JSON
236 #
237 # This method can be refined to customize the serialization by either
238 # writing pure JSON directly on the stream `v.stream` or
239 # by using other services of `JsonSerializer`.
240 #
241 # Most of the time, it is preferable to refine the method `core_serialize_to`
242 # which is used by all the serialization engines, not just JSON.
243 protected fun accept_json_serializer(v: JsonSerializer)
244 do
245 v.stream.write "\{"
246 v.indent_level += 1
247 if not v.plain_json then
248 var id = v.cache.new_id_for(self)
249 v.new_line_and_indent
250 v.stream.write "\"__kind\": \"obj\", \"__id\": "
251 v.stream.write id.to_s
252 v.stream.write ", \"__class\": \""
253 v.stream.write class_name
254 v.stream.write "\""
255 end
256 v.serialize_core(self)
257
258 v.indent_level -= 1
259 v.new_line_and_indent
260 v.stream.write "\}"
261 end
262 end
263
264 redef class Int
265 redef fun accept_json_serializer(v) do v.stream.write to_s
266 end
267
268 redef class Float
269 redef fun accept_json_serializer(v) do v.stream.write to_s
270 end
271
272 redef class Bool
273 redef fun accept_json_serializer(v) do v.stream.write to_s
274 end
275
276 redef class Char
277 redef fun accept_json_serializer(v)
278 do
279 if v.plain_json then
280 to_s.accept_json_serializer v
281 else
282 v.stream.write "\{\"__kind\": \"char\", \"__val\": "
283 to_s.accept_json_serializer v
284 v.stream.write "\}"
285 end
286 end
287 end
288
289 redef class Byte
290 redef fun accept_json_serializer(v)
291 do
292 if v.plain_json then
293 to_i.accept_json_serializer v
294 else
295 v.stream.write "\{\"__kind\": \"byte\", \"__val\": "
296 to_i.accept_json_serializer v
297 v.stream.write "\}"
298 end
299 end
300 end
301
302 redef class CString
303 redef fun accept_json_serializer(v) do to_s.accept_json_serializer(v)
304 end
305
306 redef class Collection[E]
307 # Utility to serialize a normal Json array
308 private fun serialize_to_pure_json(v: JsonSerializer)
309 do
310 v.stream.write "["
311 var is_first = true
312 for e in self do
313 if is_first then
314 is_first = false
315 else
316 v.stream.write ","
317 if v.pretty_json then v.stream.write " "
318 end
319
320 if not v.try_to_serialize(e) then
321 assert e != null # null would have been serialized
322 v.warn("element of type {e.class_name} is not serializable.")
323 v.stream.write "null"
324 end
325 end
326 v.stream.write "]"
327 end
328 end
329
330 redef class SimpleCollection[E]
331 redef fun accept_json_serializer(v)
332 do
333 if v.plain_json then
334 serialize_to_pure_json v
335 else
336 # Register as pseudo object
337 var id = v.cache.new_id_for(self)
338 v.stream.write """{"""
339 v.indent_level += 1
340 v.new_line_and_indent
341 v.stream.write """"__kind": "obj", "__id": """
342 v.stream.write id.to_s
343 v.stream.write """, "__class": """"
344 v.stream.write class_name
345 v.stream.write """","""
346 v.new_line_and_indent
347
348 v.stream.write """"__items": """
349 serialize_to_pure_json v
350
351 core_serialize_to v
352
353 v.indent_level -= 1
354 v.new_line_and_indent
355 v.stream.write "\}"
356 end
357 end
358 end
359
360 redef class Map[K, V]
361 redef fun accept_json_serializer(v)
362 do
363 # Register as pseudo object
364 var id = v.cache.new_id_for(self)
365
366 v.stream.write "\{"
367 v.indent_level += 1
368
369 if v.plain_json then
370 var first = true
371 for key, val in self do
372 if not first then
373 v.stream.write ","
374 else first = false
375 v.new_line_and_indent
376
377 var k = key or else "null"
378 k.to_s.accept_json_serializer v
379 v.stream.write ":"
380 if v.pretty_json then v.stream.write " "
381 if not v.try_to_serialize(val) then
382 assert val != null # null would have been serialized
383 v.warn("element of type {val.class_name} is not serializable.")
384 v.stream.write "null"
385 end
386 end
387 else
388 v.new_line_and_indent
389 v.stream.write """"__kind": "obj", "__id": """
390 v.stream.write id.to_s
391 v.stream.write """, "__class": """"
392 v.stream.write class_name
393 v.stream.write """", "__length": """
394 v.stream.write length.to_s
395
396 v.stream.write ","
397 v.new_line_and_indent
398 v.stream.write """"__keys": """
399 keys.serialize_to_pure_json v
400
401 v.stream.write ","
402 v.new_line_and_indent
403 v.stream.write """"__values": """
404 values.serialize_to_pure_json v
405
406 core_serialize_to v
407 end
408
409 v.indent_level -= 1
410 v.new_line_and_indent
411 v.stream.write "\}"
412 end
413 end