lib/json: there's no need for the length attribute in the generated JSON
[nit.git] / lib / json / serialization.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Handles serialization and deserialization of objects to/from JSON
18 #
19 # ## Nity JSON
20 #
21 # `JsonSerializer` write Nit objects that subclass `Serializable` to JSON,
22 # and `JsonDeserializer` can read them. They both use meta-data added to the
23 # generated JSON to recreate the Nit instances with the exact original type.
24 #
25 # For more information on Nit serialization, see: ../serialization/README.md
26 #
27 # ## Plain JSON
28 #
29 # The attribute `JsonSerializer::plain_json` triggers generating plain and
30 # clean JSON. This format is easier to read for an human and a non-Nit program,
31 # but it cannot be fully deserialized. It can still be read by services from
32 # `json::static` and `json::dynamic`.
33 #
34 # A shortcut to this service is provided by `Serializable::to_plain_json`.
35 #
36 # ### Usage Example
37 #
38 # ~~~nitish
39 # import json::serialization
40 #
41 # class Person
42 # serialize
43 #
44 # var name: String
45 # var year_of_birth: Int
46 # var next_of_kin: nullable Person
47 # end
48 #
49 # var bob = new Person("Bob", 1986)
50 # var alice = new Person("Alice", 1978, bob)
51 #
52 # assert bob.to_plain_json == """
53 # {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}"""
54 #
55 # assert alice.to_plain_json == """
56 # {"name": "Alice", "year_of_birth": 1978, "next_of_kin": {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}}"""
57 # ~~~
58 module serialization
59
60 import ::serialization::caching
61 private import ::serialization::engine_tools
62 private import static
63
64 # Serializer of Nit objects to Json string.
65 class JsonSerializer
66 super CachingSerializer
67
68 # Target writing stream
69 var stream: Writer
70
71 # Write plain JSON? easier to read but does not support Nit deserialization
72 #
73 # If `false`, the default, serialize to support deserialization:
74 #
75 # * Write meta-data, including the types of the serialized objects so they can
76 # be deserialized to their original form using `JsonDeserializer`.
77 # * Use references when an object has already been serialized so to not duplicate it.
78 # * Support cycles in references.
79 # * Preserve the Nit `Char` type as an object because it does not exist in JSON.
80 # * The generated JSON is standard and can be read by non-Nit programs.
81 # However, some Nit types are not represented by the simplest possible JSON representation.
82 # With the added meta-data, it can be complex to read.
83 #
84 # If `true`, serialize for other programs:
85 #
86 # * Nit objects are serialized to pure and standard JSON so they can
87 # be easily read by non-Nit programs and humans.
88 # * Nit objects are serialized for every references, so they can be duplicated.
89 # It is easier to read but it creates a larger output.
90 # * Does not support cycles, will replace the problematic references by `null`.
91 # * Does not serialize the meta-data needed to deserialize the objects
92 # back to regular Nit objects.
93 # * Keys of Nit `HashMap` are converted to their string reprensentation using `to_s`.
94 var plain_json = false is writable
95
96 # List of the current open objects, the first is the main target of the serialization
97 #
98 # Used only when `plain_json == true` to detect cycles in serialization.
99 private var open_objects = new Array[Object]
100
101 # Has the first attribute of the current object already been serialized?
102 #
103 # Used only when `plain_json == true`.
104 private var first_attribute = false
105
106 redef fun serialize(object)
107 do
108 if object == null then
109 stream.write "null"
110 else
111 if plain_json then
112 for o in open_objects do
113 if object.is_same_serialized(o) then
114 # Cycle detected
115 stream.write "null"
116 return
117 end
118 end
119
120 open_objects.add object
121 end
122
123 first_attribute = true
124 object.serialize_to_json self
125 first_attribute = false
126
127 if plain_json then open_objects.pop
128 end
129 end
130
131 redef fun serialize_attribute(name, value)
132 do
133 if not plain_json or not first_attribute then
134 stream.write ", "
135 first_attribute = false
136 end
137
138 stream.write "\""
139 stream.write name
140 stream.write "\": "
141 super
142 end
143
144 redef fun serialize_reference(object)
145 do
146 if not plain_json and cache.has_object(object) then
147 # if already serialized, add local reference
148 var id = cache.id_for(object)
149 stream.write "\{\"__kind\": \"ref\", \"__id\": "
150 stream.write id.to_s
151 stream.write "\}"
152 else
153 # serialize here
154 serialize object
155 end
156 end
157 end
158
159 # Deserializer from a Json string.
160 class JsonDeserializer
161 super CachingDeserializer
162
163 # Json text to deserialize from.
164 private var text: Text
165
166 # Root json object parsed from input text.
167 private var root: nullable Jsonable is noinit
168
169 # Depth-first path in the serialized object tree.
170 private var path = new Array[JsonObject]
171
172 # Last encountered object reference id.
173 #
174 # See `id_to_object`.
175 var just_opened_id: nullable Int = null
176
177 init do
178 var root = text.parse_json
179 if root isa JsonObject then path.add(root)
180 self.root = root
181 end
182
183 redef fun deserialize_attribute(name)
184 do
185 assert not path.is_empty # This is an internal error, abort
186 var current = path.last
187
188 if not current.keys.has(name) then
189 errors.add new Error("Deserialization Error: JSON object has not attribute '{name}'.")
190 return null
191 end
192
193 var value = current[name]
194
195 return convert_object(value)
196 end
197
198 # This may be called multiple times by the same object from constructors
199 # in different nclassdef
200 redef fun notify_of_creation(new_object)
201 do
202 var id = just_opened_id
203 if id == null then return # Register `new_object` only once
204 cache[id] = new_object
205 end
206
207 # Convert from simple Json object to Nit object
208 private fun convert_object(object: nullable Object): nullable Object
209 do
210 if object isa JsonParseError then
211 errors.add object
212 return null
213 end
214
215 if object isa JsonObject then
216 var kind = null
217 if object.keys.has("__kind") then
218 kind = object["__kind"]
219 end
220
221 # ref?
222 if kind == "ref" then
223 if not object.keys.has("__id") then
224 errors.add new Error("Serialization Error: JSON object reference does not declare a `__id`.")
225 return object
226 end
227
228 var id = object["__id"]
229 if not id isa Int then
230 errors.add new Error("Serialization Error: JSON object reference declares a non-integer `__id`.")
231 return object
232 end
233
234 if not cache.has_id(id) then
235 errors.add new Error("Serialization Error: JSON object reference has an unknown `__id`.")
236 return object
237 end
238
239 return cache.object_for(id)
240 end
241
242 # obj?
243 if kind == "obj" or kind == null then
244 var id = null
245 if object.keys.has("__id") then
246 id = object["__id"]
247
248 if not id isa Int then
249 errors.add new Error("Serialization Error: JSON object declaration declares a non-integer `__id`.")
250 return object
251 end
252
253 if cache.has_id(id) then
254 errors.add new Error("Serialization Error: JSON object with `__id` {id} is deserialized twice.")
255 # Keep going
256 end
257 end
258
259 if not object.keys.has("__class") then
260 errors.add new Error("Serialization Error: JSON object declaration does not declare a `__class`.")
261 return object
262 end
263
264 var class_name = object["__class"]
265 if not class_name isa String then
266 errors.add new Error("Serialization Error: JSON object declaration declares a non-string `__class`.")
267 return object
268 end
269
270 # advance on path
271 path.push object
272
273 just_opened_id = id
274 var value = deserialize_class(class_name)
275 just_opened_id = null
276
277 # revert on path
278 path.pop
279
280 return value
281 end
282
283 # char?
284 if kind == "char" then
285 if not object.keys.has("__val") then
286 errors.add new Error("Serialization Error: JSON `char` object does not declare a `__val`.")
287 return object
288 end
289
290 var val = object["__val"]
291
292 if not val isa String or val.is_empty then
293 errors.add new Error("Serialization Error: JSON `char` object does not declare a single char in `__val`.")
294 return object
295 end
296
297 return val.chars.first
298 end
299
300 errors.add new Error("Serialization Error: JSON object has an unknown `__kind`.")
301 return object
302 end
303
304 if object isa Array[nullable Object] then
305 # special case, isa Array[nullable Serializable]
306 var array = new Array[nullable Serializable]
307 for e in object do array.add e.as(nullable Serializable)
308 return array
309 end
310
311 return object
312 end
313
314 redef fun deserialize
315 do
316 errors.clear
317 return convert_object(root)
318 end
319 end
320
321 redef class Serializable
322 private fun serialize_to_json(v: JsonSerializer)
323 do
324 var id = v.cache.new_id_for(self)
325 v.stream.write "\{"
326 if not v.plain_json then
327 v.stream.write "\"__kind\": \"obj\", \"__id\": "
328 v.stream.write id.to_s
329 v.stream.write ", \"__class\": \""
330 v.stream.write class_name
331 v.stream.write "\""
332 end
333 core_serialize_to(v)
334 v.stream.write "\}"
335 end
336
337 # Serialize this object to plain JSON
338 #
339 # This is a shortcut using `JsonSerializer::plain_json`,
340 # see its documentation for more information.
341 fun to_plain_json: String
342 do
343 var stream = new StringWriter
344 var serializer = new JsonSerializer(stream)
345 serializer.plain_json = true
346 serializer.serialize self
347 stream.close
348 return stream.to_s
349 end
350 end
351
352 redef class Int
353 redef fun serialize_to_json(v) do v.stream.write(to_s)
354 end
355
356 redef class Float
357 redef fun serialize_to_json(v) do v.stream.write(to_s)
358 end
359
360 redef class Bool
361 redef fun serialize_to_json(v) do v.stream.write(to_s)
362 end
363
364 redef class Char
365 redef fun serialize_to_json(v)
366 do
367 if v.plain_json then
368 v.stream.write to_s.to_json
369 else
370 v.stream.write "\{\"__kind\": \"char\", \"__val\": "
371 v.stream.write to_s.to_json
372 v.stream.write "\}"
373 end
374 end
375 end
376
377 redef class String
378 redef fun serialize_to_json(v) do v.stream.write(to_json)
379 end
380
381 redef class NativeString
382 redef fun serialize_to_json(v) do to_s.serialize_to_json(v)
383 end
384
385 redef class Collection[E]
386 # Utility to serialize a normal Json array
387 private fun serialize_to_pure_json(v: JsonSerializer)
388 do
389 v.stream.write "["
390 var is_first = true
391 for e in self do
392 if is_first then
393 is_first = false
394 else v.stream.write ", "
395
396 if not v.try_to_serialize(e) then
397 v.warn("element of type {e.class_name} is not serializable.")
398 end
399 end
400 v.stream.write "]"
401 end
402 end
403
404 redef class SimpleCollection[E]
405 redef fun serialize_to_json(v)
406 do
407 # Register as pseudo object
408 if not v.plain_json then
409 var id = v.cache.new_id_for(self)
410 v.stream.write """{"__kind": "obj", "__id": """
411 v.stream.write id.to_s
412 v.stream.write """, "__class": """"
413 v.stream.write class_name
414 v.stream.write """", "__items": """
415 end
416
417 serialize_to_pure_json v
418
419 if not v.plain_json then
420 v.stream.write "\}"
421 end
422 end
423
424 redef init from_deserializer(v: Deserializer)
425 do
426 super
427 if v isa JsonDeserializer then
428 v.notify_of_creation self
429 init
430
431 var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
432 for o in arr do
433 var obj = v.convert_object(o)
434 self.add obj
435 end
436 end
437 end
438 end
439
440 redef class Array[E]
441 redef fun serialize_to_json(v)
442 do
443 if v.plain_json or class_name == "Array[nullable Serializable]" then
444 # Using class_name to get the exact type,
445 # we do not want Array[Int] or anything else here.
446
447 serialize_to_pure_json v
448 else super
449 end
450 end
451
452 redef class Map[K, V]
453 redef fun serialize_to_json(v)
454 do
455 # Register as pseudo object
456 var id = v.cache.new_id_for(self)
457
458 if v.plain_json then
459 v.stream.write "\{"
460 var first = true
461 for key, val in self do
462 if not first then
463 v.stream.write ", "
464 else first = false
465
466 if key == null then key = "null"
467
468 v.stream.write key.to_s.to_json
469 v.stream.write ": "
470 if not v.try_to_serialize(val) then
471 v.warn("element of type {val.class_name} is not serializable.")
472 v.stream.write "null"
473 end
474 end
475 v.stream.write "\}"
476 else
477 v.stream.write """{"__kind": "obj", "__id": """
478 v.stream.write id.to_s
479 v.stream.write """, "__class": """"
480 v.stream.write class_name
481 v.stream.write """", "__length": """
482 v.stream.write length.to_s
483
484 v.stream.write """, "__keys": """
485 keys.serialize_to_pure_json v
486
487 v.stream.write """, "__values": """
488 values.serialize_to_pure_json v
489
490 v.stream.write "\}"
491 end
492 end
493
494 # Instantiate a new `Array` from its serialized representation.
495 redef init from_deserializer(v: Deserializer)
496 do
497 super
498
499 if v isa JsonDeserializer then
500 v.notify_of_creation self
501 init
502
503 var length = v.deserialize_attribute("__length").as(Int)
504 var keys = v.path.last["__keys"].as(SequenceRead[nullable Object])
505 var values = v.path.last["__values"].as(SequenceRead[nullable Object])
506 for i in length.times do
507 var key = v.convert_object(keys[i])
508 var value = v.convert_object(values[i])
509 self[key] = value
510 end
511 end
512 end
513 end