tests: add some runtime error in nitin.input
[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` type as an object because it does not exist in JSON.
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 fun serialize(object)
73 do
74 if object == null then
75 stream.write "null"
76 else
77 if plain_json then
78 for o in open_objects do
79 if object.is_same_serialized(o) then
80 # Cycle, can't be managed in plain json
81 warn "Cycle detected in serialized object, replacing reference with 'null'."
82 stream.write "null"
83 return
84 end
85 end
86
87 open_objects.add object
88 end
89
90 first_attribute = true
91 object.accept_json_serializer self
92 first_attribute = false
93
94 if plain_json then open_objects.pop
95 end
96 end
97
98 redef fun serialize_attribute(name, value)
99 do
100 if not plain_json or not first_attribute then
101 stream.write ","
102 end
103 first_attribute = false
104
105 new_line_and_indent
106 stream.write "\""
107 stream.write name
108 stream.write "\":"
109 if pretty_json then stream.write " "
110 super
111 end
112
113 redef fun serialize_reference(object)
114 do
115 if not plain_json and cache.has_object(object) then
116 # if already serialized, add local reference
117 var id = cache.id_for(object)
118 stream.write "\{"
119 indent_level += 1
120 new_line_and_indent
121 stream.write "\"__kind\": \"ref\", \"__id\": "
122 stream.write id.to_s
123 indent_level -= 1
124 new_line_and_indent
125 stream.write "\}"
126 else
127 # serialize here
128 serialize object
129 end
130 end
131
132 # Write a new line and indent it, only if `pretty_json`
133 private fun new_line_and_indent
134 do
135 if pretty_json then
136 stream.write "\n"
137 for i in indent_level.times do stream.write "\t"
138 end
139 end
140 end
141
142 redef class Text
143
144 redef fun accept_json_serializer(v)
145 do
146 v.stream.write "\""
147
148 var start_i = 0
149 var escaped = null
150 for i in [0 .. self.length[ do
151 var char = self[i]
152 if char == '\\' then
153 escaped = "\\\\"
154 else if char == '\"' then
155 escaped = "\\\""
156 else if char < ' ' then
157 if char == '\n' then
158 escaped = "\\n"
159 else if char == '\r' then
160 escaped = "\\r"
161 else if char == '\t' then
162 escaped = "\\t"
163 else
164 escaped = char.escape_to_utf16
165 end
166 end
167
168 if escaped != null then
169 # Write open non-escaped string
170 if start_i <= i then
171 v.stream.write substring(start_i, i-start_i)
172 end
173
174 # Write escaped character
175 v.stream.write escaped
176 escaped = null
177 start_i = i+1
178 end
179 end
180
181 # Write remaining non-escaped string
182 if start_i < length then
183 if start_i == 0 then
184 v.stream.write self
185 else
186 v.stream.write substring(start_i, length-start_i)
187 end
188 end
189
190 v.stream.write "\""
191 end
192 end
193
194 redef class Serializable
195
196 # Serialize `self` to JSON
197 #
198 # Set `plain = true` to generate standard JSON, without deserialization metadata.
199 # Use this option if the generated JSON will be read by other programs or humans.
200 # Use the default, `plain = false`, if the JSON is to be deserialized by a Nit program.
201 #
202 # Set `pretty = true` to generate pretty JSON for human eyes.
203 # Use the default, `pretty = false`, to generate minified JSON.
204 #
205 # This method should not be refined by subclasses,
206 # instead `accept_json_serializer` can customize the serialization of an object.
207 #
208 # See: `JsonSerializer`
209 fun serialize_to_json(plain, pretty: nullable Bool): String
210 do
211 var stream = new StringWriter
212 var serializer = new JsonSerializer(stream)
213 serializer.plain_json = plain or else false
214 serializer.pretty_json = pretty or else false
215 serializer.serialize self
216 stream.close
217 return stream.to_s
218 end
219
220 # Serialize `self` to plain JSON
221 #
222 # Compatibility alias for `serialize_to_json(plain=true)`.
223 fun to_json: String do return serialize_to_json(plain=true)
224
225 # Serialize `self` to plain pretty JSON
226 #
227 # Compatibility alias for `serialize_to_json(plain=true, pretty=true)`.
228 fun to_pretty_json: String do return serialize_to_json(plain=true, pretty=true)
229
230 # Refinable service to customize the serialization of this class to JSON
231 #
232 # This method can be refined to customize the serialization by either
233 # writing pure JSON directly on the stream `v.stream` or
234 # by using other services of `JsonSerializer`.
235 #
236 # Most of the time, it is preferable to refine the method `core_serialize_to`
237 # which is used by all the serialization engines, not just JSON.
238 protected fun accept_json_serializer(v: JsonSerializer)
239 do
240 var id = v.cache.new_id_for(self)
241 v.stream.write "\{"
242 v.indent_level += 1
243 if not v.plain_json then
244 v.new_line_and_indent
245 v.stream.write "\"__kind\": \"obj\", \"__id\": "
246 v.stream.write id.to_s
247 v.stream.write ", \"__class\": \""
248 v.stream.write class_name
249 v.stream.write "\""
250 end
251 core_serialize_to(v)
252
253 v.indent_level -= 1
254 v.new_line_and_indent
255 v.stream.write "\}"
256 end
257 end
258
259 redef class Int
260 redef fun accept_json_serializer(v) do v.stream.write to_s
261 end
262
263 redef class Float
264 redef fun accept_json_serializer(v) do v.stream.write to_s
265 end
266
267 redef class Bool
268 redef fun accept_json_serializer(v) do v.stream.write to_s
269 end
270
271 redef class Char
272 redef fun accept_json_serializer(v)
273 do
274 if v.plain_json then
275 to_s.accept_json_serializer v
276 else
277 v.stream.write "\{\"__kind\": \"char\", \"__val\": "
278 to_s.accept_json_serializer v
279 v.stream.write "\}"
280 end
281 end
282 end
283
284 redef class CString
285 redef fun accept_json_serializer(v) do to_s.accept_json_serializer(v)
286 end
287
288 redef class Collection[E]
289 # Utility to serialize a normal Json array
290 private fun serialize_to_pure_json(v: JsonSerializer)
291 do
292 v.stream.write "["
293 var is_first = true
294 for e in self do
295 if is_first then
296 is_first = false
297 else
298 v.stream.write ","
299 if v.pretty_json then v.stream.write " "
300 end
301
302 if not v.try_to_serialize(e) then
303 assert e != null # null would have been serialized
304 v.warn("element of type {e.class_name} is not serializable.")
305 v.stream.write "null"
306 end
307 end
308 v.stream.write "]"
309 end
310 end
311
312 redef class SimpleCollection[E]
313 redef fun accept_json_serializer(v)
314 do
315 # Register as pseudo object
316 if not v.plain_json then
317 var id = v.cache.new_id_for(self)
318 v.stream.write """{"""
319 v.indent_level += 1
320 v.new_line_and_indent
321 v.stream.write """"__kind": "obj", "__id": """
322 v.stream.write id.to_s
323 v.stream.write """, "__class": """"
324 v.stream.write class_name
325 v.stream.write """","""
326 v.new_line_and_indent
327 v.stream.write """"__items": """
328 serialize_to_pure_json v
329 core_serialize_to v
330 else
331 serialize_to_pure_json v
332 end
333
334 if not v.plain_json then
335 v.indent_level -= 1
336 v.new_line_and_indent
337 v.stream.write "\}"
338 end
339 end
340 end
341
342 redef class Map[K, V]
343 redef fun accept_json_serializer(v)
344 do
345 # Register as pseudo object
346 var id = v.cache.new_id_for(self)
347
348 v.stream.write "\{"
349 v.indent_level += 1
350
351 if v.plain_json then
352 var first = true
353 for key, val in self do
354 if not first then
355 v.stream.write ","
356 else first = false
357 v.new_line_and_indent
358
359 var k = key or else "null"
360 k.to_s.accept_json_serializer v
361 v.stream.write ":"
362 if v.pretty_json then v.stream.write " "
363 if not v.try_to_serialize(val) then
364 assert val != null # null would have been serialized
365 v.warn("element of type {val.class_name} is not serializable.")
366 v.stream.write "null"
367 end
368 end
369 else
370 v.new_line_and_indent
371 v.stream.write """"__kind": "obj", "__id": """
372 v.stream.write id.to_s
373 v.stream.write """, "__class": """"
374 v.stream.write class_name
375 v.stream.write """", "__length": """
376 v.stream.write length.to_s
377
378 v.stream.write ","
379 v.new_line_and_indent
380 v.stream.write """"__keys": """
381 keys.serialize_to_pure_json v
382
383 v.stream.write ","
384 v.new_line_and_indent
385 v.stream.write """"__values": """
386 values.serialize_to_pure_json v
387
388 core_serialize_to v
389 end
390
391 v.indent_level -= 1
392 v.new_line_and_indent
393 v.stream.write "\}"
394 end
395 end