lib/serialization: serialize all insances of Text as we did with srings
[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 #
59 # ## JSON to Nit objects
60 #
61 # The `JsonDeserializer` support reading JSON code with minimal meta-data
62 # to easily create Nit object from client-side code or configuration files.
63 # Each JSON object must define the `__class` attribute with the corresponding
64 # Nit class and the expected attributes with its name in Nit followed by its value.
65 #
66 # ### Usage Example
67 #
68 # ~~~nitish
69 # import json::serialization
70 #
71 # class MeetupConfig
72 # serialize
73 #
74 # var description: String
75 # var max_participants: nullable Int
76 # var answers: Array[FlatString]
77 # end
78 #
79 # var json_code = """
80 # {"__class": "MeetupConfig", "description": "My Awesome Meetup", "max_participants": null, "answers": ["Pepperoni", "Chicken"]}"""
81 # var deserializer = new JsonDeserializer(json_code)
82 #
83 # var meet = deserializer.deserialize
84 # assert meet isa MeetupConfig
85 # assert meet.description == "My Awesome Meetup"
86 # assert meet.max_participants == null
87 # assert meet.answers == ["Pepperoni", "Chicken"]
88 # ~~~
89 module serialization
90
91 import ::serialization::caching
92 private import ::serialization::engine_tools
93 import static
94
95 # Serializer of Nit objects to Json string.
96 class JsonSerializer
97 super CachingSerializer
98
99 # Target writing stream
100 var stream: Writer
101
102 # Write plain JSON? easier to read but does not support Nit deserialization
103 #
104 # If `false`, the default, serialize to support deserialization:
105 #
106 # * Write meta-data, including the types of the serialized objects so they can
107 # be deserialized to their original form using `JsonDeserializer`.
108 # * Use references when an object has already been serialized so to not duplicate it.
109 # * Support cycles in references.
110 # * Preserve the Nit `Char` type as an object because it does not exist in JSON.
111 # * The generated JSON is standard and can be read by non-Nit programs.
112 # However, some Nit types are not represented by the simplest possible JSON representation.
113 # With the added meta-data, it can be complex to read.
114 #
115 # If `true`, serialize for other programs:
116 #
117 # * Nit objects are serialized to pure and standard JSON so they can
118 # be easily read by non-Nit programs and humans.
119 # * Nit objects are serialized for every references, so they can be duplicated.
120 # It is easier to read but it creates a larger output.
121 # * Does not support cycles, will replace the problematic references by `null`.
122 # * Does not serialize the meta-data needed to deserialize the objects
123 # back to regular Nit objects.
124 # * Keys of Nit `HashMap` are converted to their string reprensentation using `to_s`.
125 var plain_json = false is writable
126
127 # List of the current open objects, the first is the main target of the serialization
128 #
129 # Used only when `plain_json == true` to detect cycles in serialization.
130 private var open_objects = new Array[Object]
131
132 # Has the first attribute of the current object already been serialized?
133 #
134 # Used only when `plain_json == true`.
135 private var first_attribute = false
136
137 redef fun serialize(object)
138 do
139 if object == null then
140 stream.write "null"
141 else
142 if plain_json then
143 for o in open_objects do
144 if object.is_same_serialized(o) then
145 # Cycle detected
146 stream.write "null"
147 return
148 end
149 end
150
151 open_objects.add object
152 end
153
154 first_attribute = true
155 object.serialize_to_json self
156 first_attribute = false
157
158 if plain_json then open_objects.pop
159 end
160 end
161
162 redef fun serialize_attribute(name, value)
163 do
164 if not plain_json or not first_attribute then
165 stream.write ", "
166 first_attribute = false
167 end
168
169 stream.write "\""
170 stream.write name
171 stream.write "\": "
172 super
173 end
174
175 redef fun serialize_reference(object)
176 do
177 if not plain_json and cache.has_object(object) then
178 # if already serialized, add local reference
179 var id = cache.id_for(object)
180 stream.write "\{\"__kind\": \"ref\", \"__id\": "
181 stream.write id.to_s
182 stream.write "\}"
183 else
184 # serialize here
185 serialize object
186 end
187 end
188 end
189
190 # Deserializer from a Json string.
191 class JsonDeserializer
192 super CachingDeserializer
193
194 # Json text to deserialize from.
195 private var text: Text
196
197 # Root json object parsed from input text.
198 private var root: nullable Jsonable is noinit
199
200 # Depth-first path in the serialized object tree.
201 private var path = new Array[JsonObject]
202
203 # Last encountered object reference id.
204 #
205 # See `id_to_object`.
206 var just_opened_id: nullable Int = null
207
208 init do
209 var root = text.parse_json
210 if root isa JsonObject then path.add(root)
211 self.root = root
212 end
213
214 redef fun deserialize_attribute(name)
215 do
216 assert not path.is_empty # This is an internal error, abort
217 var current = path.last
218
219 if not current.keys.has(name) then
220 errors.add new Error("Deserialization Error: JSON object has not attribute '{name}'.")
221 return null
222 end
223
224 var value = current[name]
225
226 return convert_object(value)
227 end
228
229 # This may be called multiple times by the same object from constructors
230 # in different nclassdef
231 redef fun notify_of_creation(new_object)
232 do
233 var id = just_opened_id
234 if id == null then return # Register `new_object` only once
235 cache[id] = new_object
236 end
237
238 # Convert from simple Json object to Nit object
239 private fun convert_object(object: nullable Object): nullable Object
240 do
241 if object isa JsonParseError then
242 errors.add object
243 return null
244 end
245
246 if object isa JsonObject then
247 var kind = null
248 if object.keys.has("__kind") then
249 kind = object["__kind"]
250 end
251
252 # ref?
253 if kind == "ref" then
254 if not object.keys.has("__id") then
255 errors.add new Error("Serialization Error: JSON object reference does not declare a `__id`.")
256 return object
257 end
258
259 var id = object["__id"]
260 if not id isa Int then
261 errors.add new Error("Serialization Error: JSON object reference declares a non-integer `__id`.")
262 return object
263 end
264
265 if not cache.has_id(id) then
266 errors.add new Error("Serialization Error: JSON object reference has an unknown `__id`.")
267 return object
268 end
269
270 return cache.object_for(id)
271 end
272
273 # obj?
274 if kind == "obj" or kind == null then
275 var id = null
276 if object.keys.has("__id") then
277 id = object["__id"]
278
279 if not id isa Int then
280 errors.add new Error("Serialization Error: JSON object declaration declares a non-integer `__id`.")
281 return object
282 end
283
284 if cache.has_id(id) then
285 errors.add new Error("Serialization Error: JSON object with `__id` {id} is deserialized twice.")
286 # Keep going
287 end
288 end
289
290 var class_name = object.get_or_null("__class")
291 if class_name == null then
292 # Fallback to custom heuristic
293 class_name = class_name_heuristic(object)
294 end
295
296 if class_name == null then
297 errors.add new Error("Serialization Error: JSON object declaration does not declare a `__class`.")
298 return object
299 end
300
301 if not class_name isa String then
302 errors.add new Error("Serialization Error: JSON object declaration declares a non-string `__class`.")
303 return object
304 end
305
306 # advance on path
307 path.push object
308
309 just_opened_id = id
310 var value = deserialize_class(class_name)
311 just_opened_id = null
312
313 # revert on path
314 path.pop
315
316 return value
317 end
318
319 # char?
320 if kind == "char" then
321 if not object.keys.has("__val") then
322 errors.add new Error("Serialization Error: JSON `char` object does not declare a `__val`.")
323 return object
324 end
325
326 var val = object["__val"]
327
328 if not val isa String or val.is_empty then
329 errors.add new Error("Serialization Error: JSON `char` object does not declare a single char in `__val`.")
330 return object
331 end
332
333 return val.chars.first
334 end
335
336 errors.add new Error("Serialization Error: JSON object has an unknown `__kind`.")
337 return object
338 end
339
340 # Simple JSON array without serialization metadata
341 if object isa Array[nullable Object] then
342 var array = new Array[nullable Object]
343 var types = new HashSet[String]
344 var has_nullable = false
345 for e in object do
346 var res = convert_object(e)
347 array.add res
348
349 if res != null then
350 types.add res.class_name
351 else has_nullable = true
352 end
353
354 if types.length == 1 then
355 var array_type = types.first
356
357 var typed_array
358 if array_type == "FlatString" then
359 if has_nullable then
360 typed_array = new Array[nullable FlatString]
361 else typed_array = new Array[FlatString]
362 else if array_type == "Int" then
363 if has_nullable then
364 typed_array = new Array[nullable Int]
365 else typed_array = new Array[Int]
366 else if array_type == "Float" then
367 if has_nullable then
368 typed_array = new Array[nullable Float]
369 else typed_array = new Array[Float]
370 else
371 # TODO support all array types when we separate the constructor
372 # `from_deserializer` from the filling of the items.
373
374 if not has_nullable then
375 typed_array = new Array[Object]
376 else
377 # Unsupported array type, return as `Array[nullable Object]`
378 return array
379 end
380 end
381
382 assert typed_array isa Array[nullable Object]
383
384 # Copy item to the new array
385 for e in array do typed_array.add e
386 return typed_array
387 end
388
389 # Uninferable type, return as `Array[nullable Object]`
390 return array
391 end
392
393 return object
394 end
395
396 redef fun deserialize
397 do
398 errors.clear
399 return convert_object(root)
400 end
401
402 # User customizable heuristic to get the name of the Nit class to deserialize `json_object`
403 #
404 # This method is called only when deserializing an object without the metadata `__class`.
405 # Return the class name as a `String` when it can be inferred.
406 # Return `null` when the class name cannot be found.
407 #
408 # If a valid class name is returned, `json_object` will then be deserialized normally.
409 # So it must contain the attributes of the corresponding class, as usual.
410 #
411 # ~~~nitish
412 # class MyData
413 # serialize
414 #
415 # var data: String
416 # end
417 #
418 # class MyError
419 # serialize
420 #
421 # var error: String
422 # end
423 #
424 # class MyJsonDeserializer
425 # super JsonDeserializer
426 #
427 # redef fun class_name_heuristic(json_object)
428 # do
429 # if json_object.keys.has("error") then return "MyError"
430 # if json_object.keys.has("data") then return "MyData"
431 # return null
432 # end
433 # end
434 #
435 # var json = """{"data": "some other data"}"""
436 # var deserializer = new MyJsonDeserializer(json)
437 # var deserialized = deserializer.deserialize
438 # assert deserialized isa MyData
439 #
440 # json = """{"error": "some error message"}"""
441 # deserializer = new MyJsonDeserializer(json)
442 # deserialized = deserializer.deserialize
443 # assert deserialized isa MyError
444 # ~~~
445 protected fun class_name_heuristic(json_object: JsonObject): nullable String
446 do
447 return null
448 end
449 end
450
451 redef class Text
452
453 # Deserialize a `nullable Object` from this JSON formatted string
454 #
455 # Warning: Deserialization errors are reported with `print_error` and
456 # may be returned as a partial object or as `null`.
457 #
458 # This method is not appropriate when errors need to be handled programmatically,
459 # manually use a `JsonDeserializer` in such cases.
460 fun from_json_string: nullable Object
461 do
462 var deserializer = new JsonDeserializer(self)
463 var res = deserializer.deserialize
464 if deserializer.errors.not_empty then
465 print_error "Deserialization Errors: {deserializer.errors.join(", ")}"
466 end
467 return res
468 end
469
470 redef fun serialize_to_json(v) do v.stream.write(to_json)
471 end
472
473 redef class Serializable
474 private fun serialize_to_json(v: JsonSerializer)
475 do
476 var id = v.cache.new_id_for(self)
477 v.stream.write "\{"
478 if not v.plain_json then
479 v.stream.write "\"__kind\": \"obj\", \"__id\": "
480 v.stream.write id.to_s
481 v.stream.write ", \"__class\": \""
482 v.stream.write class_name
483 v.stream.write "\""
484 end
485 core_serialize_to(v)
486 v.stream.write "\}"
487 end
488
489 # Serialize this object to a JSON string with metadata for deserialization
490 fun to_json_string: String
491 do
492 var stream = new StringWriter
493 var serializer = new JsonSerializer(stream)
494 serializer.serialize self
495 stream.close
496 return stream.to_s
497 end
498
499 # Serialize this object to plain JSON
500 #
501 # This is a shortcut using `JsonSerializer::plain_json`,
502 # see its documentation for more information.
503 fun to_plain_json: String
504 do
505 var stream = new StringWriter
506 var serializer = new JsonSerializer(stream)
507 serializer.plain_json = true
508 serializer.serialize self
509 stream.close
510 return stream.to_s
511 end
512 end
513
514 redef class Int
515 redef fun serialize_to_json(v) do v.stream.write(to_s)
516 end
517
518 redef class Float
519 redef fun serialize_to_json(v) do v.stream.write(to_s)
520 end
521
522 redef class Bool
523 redef fun serialize_to_json(v) do v.stream.write(to_s)
524 end
525
526 redef class Char
527 redef fun serialize_to_json(v)
528 do
529 if v.plain_json then
530 v.stream.write to_s.to_json
531 else
532 v.stream.write "\{\"__kind\": \"char\", \"__val\": "
533 v.stream.write to_s.to_json
534 v.stream.write "\}"
535 end
536 end
537 end
538
539 redef class NativeString
540 redef fun serialize_to_json(v) do to_s.serialize_to_json(v)
541 end
542
543 redef class Collection[E]
544 # Utility to serialize a normal Json array
545 private fun serialize_to_pure_json(v: JsonSerializer)
546 do
547 v.stream.write "["
548 var is_first = true
549 for e in self do
550 if is_first then
551 is_first = false
552 else v.stream.write ", "
553
554 if not v.try_to_serialize(e) then
555 v.warn("element of type {e.class_name} is not serializable.")
556 end
557 end
558 v.stream.write "]"
559 end
560 end
561
562 redef class SimpleCollection[E]
563 redef fun serialize_to_json(v)
564 do
565 # Register as pseudo object
566 if not v.plain_json then
567 var id = v.cache.new_id_for(self)
568 v.stream.write """{"__kind": "obj", "__id": """
569 v.stream.write id.to_s
570 v.stream.write """, "__class": """"
571 v.stream.write class_name
572 v.stream.write """", "__items": """
573 end
574
575 serialize_to_pure_json v
576
577 if not v.plain_json then
578 v.stream.write "\}"
579 end
580 end
581
582 redef init from_deserializer(v)
583 do
584 super
585 if v isa JsonDeserializer then
586 v.notify_of_creation self
587 init
588
589 var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
590 for o in arr do
591 var obj = v.convert_object(o)
592 self.add obj
593 end
594 end
595 end
596 end
597
598 redef class Map[K, V]
599 redef fun serialize_to_json(v)
600 do
601 # Register as pseudo object
602 var id = v.cache.new_id_for(self)
603
604 if v.plain_json then
605 v.stream.write "\{"
606 var first = true
607 for key, val in self do
608 if not first then
609 v.stream.write ", "
610 else first = false
611
612 if key == null then key = "null"
613
614 v.stream.write key.to_s.to_json
615 v.stream.write ": "
616 if not v.try_to_serialize(val) then
617 v.warn("element of type {val.class_name} is not serializable.")
618 v.stream.write "null"
619 end
620 end
621 v.stream.write "\}"
622 else
623 v.stream.write """{"__kind": "obj", "__id": """
624 v.stream.write id.to_s
625 v.stream.write """, "__class": """"
626 v.stream.write class_name
627 v.stream.write """", "__length": """
628 v.stream.write length.to_s
629
630 v.stream.write """, "__keys": """
631 keys.serialize_to_pure_json v
632
633 v.stream.write """, "__values": """
634 values.serialize_to_pure_json v
635
636 v.stream.write "\}"
637 end
638 end
639
640 redef init from_deserializer(v)
641 do
642 super
643
644 if v isa JsonDeserializer then
645 v.notify_of_creation self
646 init
647
648 var length = v.deserialize_attribute("__length").as(Int)
649 var keys = v.path.last["__keys"].as(SequenceRead[nullable Object])
650 var values = v.path.last["__values"].as(SequenceRead[nullable Object])
651 for i in length.times do
652 var key = v.convert_object(keys[i])
653 var value = v.convert_object(values[i])
654 self[key] = value
655 end
656 end
657 end
658 end