9227dbf3cbe6c85d0252ab38a4d11d38c0f09482
[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 private 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? Standard JSON without metadata for 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 representation using `to_s`.
125 var plain_json = false is writable
126
127 # Write pretty JSON for human eyes?
128 #
129 # Toggles skipping lines between attributes of an object and
130 # properly indent the written JSON.
131 var pretty_json = false is writable
132
133 # Current indentation level used for writing `pretty_json`
134 private var indent_level = 0
135
136 # List of the current open objects, the first is the main target of the serialization
137 #
138 # Used only when `plain_json == true` to detect cycles in serialization.
139 private var open_objects = new Array[Object]
140
141 # Has the first attribute of the current object already been serialized?
142 #
143 # Used only when `plain_json == true`.
144 private var first_attribute = false
145
146 redef fun serialize(object)
147 do
148 if object == null then
149 stream.write "null"
150 else
151 if plain_json then
152 for o in open_objects do
153 if object.is_same_serialized(o) then
154 # Cycle, can't be managed in plain json
155 warn "Cycle detected in serialized object, replacing reference with 'null'."
156 stream.write "null"
157 return
158 end
159 end
160
161 open_objects.add object
162 end
163
164 first_attribute = true
165 object.accept_json_serializer self
166 first_attribute = false
167
168 if plain_json then open_objects.pop
169 end
170 end
171
172 redef fun serialize_attribute(name, value)
173 do
174 if not plain_json or not first_attribute then
175 stream.write ","
176 first_attribute = false
177 end
178
179 new_line_and_indent
180 stream.write "\""
181 stream.write name
182 stream.write "\": "
183 super
184 end
185
186 redef fun serialize_reference(object)
187 do
188 if not plain_json and cache.has_object(object) then
189 # if already serialized, add local reference
190 var id = cache.id_for(object)
191 stream.write "\{"
192 indent_level += 1
193 new_line_and_indent
194 stream.write "\"__kind\": \"ref\", \"__id\": "
195 stream.write id.to_s
196 indent_level -= 1
197 new_line_and_indent
198 stream.write "\}"
199 else
200 # serialize here
201 serialize object
202 end
203 end
204
205 # Write a new line and indent it, only if `pretty_json`
206 private fun new_line_and_indent
207 do
208 if pretty_json then
209 stream.write "\n"
210 for i in indent_level.times do stream.write "\t"
211 end
212 end
213 end
214
215 # Deserializer from a Json string.
216 class JsonDeserializer
217 super CachingDeserializer
218
219 # Json text to deserialize from.
220 private var text: Text
221
222 # Root json object parsed from input text.
223 private var root: nullable Object is noinit
224
225 # Depth-first path in the serialized object tree.
226 private var path = new Array[Map[String, nullable Object]]
227
228 # Last encountered object reference id.
229 #
230 # See `id_to_object`.
231 var just_opened_id: nullable Int = null
232
233 init do
234 var root = text.parse_json
235 if root isa Map[String, nullable Object] then path.add(root)
236 self.root = root
237 end
238
239 redef fun deserialize_attribute(name)
240 do
241 assert not path.is_empty # This is an internal error, abort
242 var current = path.last
243
244 if not current.keys.has(name) then
245 errors.add new Error("Deserialization Error: JSON object has not attribute '{name}'.")
246 return null
247 end
248
249 var value = current[name]
250
251 return convert_object(value)
252 end
253
254 # This may be called multiple times by the same object from constructors
255 # in different nclassdef
256 redef fun notify_of_creation(new_object)
257 do
258 var id = just_opened_id
259 if id == null then return # Register `new_object` only once
260 cache[id] = new_object
261 end
262
263 # Convert from simple Json object to Nit object
264 private fun convert_object(object: nullable Object): nullable Object
265 do
266 if object isa JsonParseError then
267 errors.add object
268 return null
269 end
270
271 if object isa Map[String, nullable Object] then
272 var kind = null
273 if object.keys.has("__kind") then
274 kind = object["__kind"]
275 end
276
277 # ref?
278 if kind == "ref" then
279 if not object.keys.has("__id") then
280 errors.add new Error("Serialization Error: JSON object reference does not declare a `__id`.")
281 return object
282 end
283
284 var id = object["__id"]
285 if not id isa Int then
286 errors.add new Error("Serialization Error: JSON object reference declares a non-integer `__id`.")
287 return object
288 end
289
290 if not cache.has_id(id) then
291 errors.add new Error("Serialization Error: JSON object reference has an unknown `__id`.")
292 return object
293 end
294
295 return cache.object_for(id)
296 end
297
298 # obj?
299 if kind == "obj" or kind == null then
300 var id = null
301 if object.keys.has("__id") then
302 id = object["__id"]
303
304 if not id isa Int then
305 errors.add new Error("Serialization Error: JSON object declaration declares a non-integer `__id`.")
306 return object
307 end
308
309 if cache.has_id(id) then
310 errors.add new Error("Serialization Error: JSON object with `__id` {id} is deserialized twice.")
311 # Keep going
312 end
313 end
314
315 var class_name = object.get_or_null("__class")
316 if class_name == null then
317 # Fallback to custom heuristic
318 class_name = class_name_heuristic(object)
319 end
320
321 if class_name == null then
322 errors.add new Error("Serialization Error: JSON object declaration does not declare a `__class`.")
323 return object
324 end
325
326 if not class_name isa String then
327 errors.add new Error("Serialization Error: JSON object declaration declares a non-string `__class`.")
328 return object
329 end
330
331 # advance on path
332 path.push object
333
334 just_opened_id = id
335 var value = deserialize_class(class_name)
336 just_opened_id = null
337
338 # revert on path
339 path.pop
340
341 return value
342 end
343
344 # char?
345 if kind == "char" then
346 if not object.keys.has("__val") then
347 errors.add new Error("Serialization Error: JSON `char` object does not declare a `__val`.")
348 return object
349 end
350
351 var val = object["__val"]
352
353 if not val isa String or val.is_empty then
354 errors.add new Error("Serialization Error: JSON `char` object does not declare a single char in `__val`.")
355 return object
356 end
357
358 return val.chars.first
359 end
360
361 errors.add new Error("Serialization Error: JSON object has an unknown `__kind`.")
362 return object
363 end
364
365 # Simple JSON array without serialization metadata
366 if object isa Array[nullable Object] then
367 var array = new Array[nullable Object]
368 var types = new HashSet[String]
369 var has_nullable = false
370 for e in object do
371 var res = convert_object(e)
372 array.add res
373
374 if res != null then
375 types.add res.class_name
376 else has_nullable = true
377 end
378
379 if types.length == 1 then
380 var array_type = types.first
381
382 var typed_array
383 if array_type == "ASCIIFlatString" or array_type == "UnicodeFlatString" then
384 if has_nullable then
385 typed_array = new Array[nullable FlatString]
386 else typed_array = new Array[FlatString]
387 else if array_type == "Int" then
388 if has_nullable then
389 typed_array = new Array[nullable Int]
390 else typed_array = new Array[Int]
391 else if array_type == "Float" then
392 if has_nullable then
393 typed_array = new Array[nullable Float]
394 else typed_array = new Array[Float]
395 else
396 # TODO support all array types when we separate the constructor
397 # `from_deserializer` from the filling of the items.
398
399 if not has_nullable then
400 typed_array = new Array[Object]
401 else
402 # Unsupported array type, return as `Array[nullable Object]`
403 return array
404 end
405 end
406
407 assert typed_array isa Array[nullable Object]
408
409 # Copy item to the new array
410 for e in array do typed_array.add e
411 return typed_array
412 end
413
414 # Uninferable type, return as `Array[nullable Object]`
415 return array
416 end
417
418 return object
419 end
420
421 redef fun deserialize
422 do
423 errors.clear
424 return convert_object(root)
425 end
426
427 # User customizable heuristic to get the name of the Nit class to deserialize `json_object`
428 #
429 # This method is called only when deserializing an object without the metadata `__class`.
430 # Return the class name as a `String` when it can be inferred.
431 # Return `null` when the class name cannot be found.
432 #
433 # If a valid class name is returned, `json_object` will then be deserialized normally.
434 # So it must contain the attributes of the corresponding class, as usual.
435 #
436 # ~~~nitish
437 # class MyData
438 # serialize
439 #
440 # var data: String
441 # end
442 #
443 # class MyError
444 # serialize
445 #
446 # var error: String
447 # end
448 #
449 # class MyJsonDeserializer
450 # super JsonDeserializer
451 #
452 # redef fun class_name_heuristic(json_object)
453 # do
454 # if json_object.keys.has("error") then return "MyError"
455 # if json_object.keys.has("data") then return "MyData"
456 # return null
457 # end
458 # end
459 #
460 # var json = """{"data": "some other data"}"""
461 # var deserializer = new MyJsonDeserializer(json)
462 # var deserialized = deserializer.deserialize
463 # assert deserialized isa MyData
464 #
465 # json = """{"error": "some error message"}"""
466 # deserializer = new MyJsonDeserializer(json)
467 # deserialized = deserializer.deserialize
468 # assert deserialized isa MyError
469 # ~~~
470 protected fun class_name_heuristic(json_object: Map[String, nullable Object]): nullable String
471 do
472 return null
473 end
474 end
475
476 redef class Text
477
478 # Deserialize a `nullable Object` from this JSON formatted string
479 #
480 # Warning: Deserialization errors are reported with `print_error` and
481 # may be returned as a partial object or as `null`.
482 #
483 # This method is not appropriate when errors need to be handled programmatically,
484 # manually use a `JsonDeserializer` in such cases.
485 fun from_json_string: nullable Object
486 do
487 var deserializer = new JsonDeserializer(self)
488 var res = deserializer.deserialize
489 if deserializer.errors.not_empty then
490 print_error "Deserialization Errors: {deserializer.errors.join(", ")}"
491 end
492 return res
493 end
494
495 redef fun accept_json_serializer(v) do v.stream.write(to_json)
496 end
497
498 redef class Serializable
499 private fun accept_json_serializer(v: JsonSerializer)
500 do
501 var id = v.cache.new_id_for(self)
502 v.stream.write "\{"
503 v.indent_level += 1
504 if not v.plain_json then
505 v.new_line_and_indent
506 v.stream.write "\"__kind\": \"obj\", \"__id\": "
507 v.stream.write id.to_s
508 v.stream.write ", \"__class\": \""
509 v.stream.write class_name
510 v.stream.write "\""
511 end
512 core_serialize_to(v)
513
514 v.indent_level -= 1
515 v.new_line_and_indent
516 v.stream.write "\}"
517 end
518
519 # Serialize this object to a JSON string with metadata for deserialization
520 fun to_json_string: String
521 do
522 var stream = new StringWriter
523 var serializer = new JsonSerializer(stream)
524 serializer.serialize self
525 stream.close
526 return stream.to_s
527 end
528
529 # Serialize this object to plain JSON
530 #
531 # This is a shortcut using `JsonSerializer::plain_json`,
532 # see its documentation for more information.
533 fun to_plain_json: String
534 do
535 var stream = new StringWriter
536 var serializer = new JsonSerializer(stream)
537 serializer.plain_json = true
538 serializer.serialize self
539 stream.close
540 return stream.to_s
541 end
542 end
543
544 redef class Int
545 redef fun accept_json_serializer(v) do v.stream.write to_s
546 end
547
548 redef class Float
549 redef fun accept_json_serializer(v) do v.stream.write to_s
550 end
551
552 redef class Bool
553 redef fun accept_json_serializer(v) do v.stream.write to_s
554 end
555
556 redef class Char
557 redef fun accept_json_serializer(v)
558 do
559 if v.plain_json then
560 to_s.accept_json_serializer v
561 else
562 v.stream.write "\{\"__kind\": \"char\", \"__val\": "
563 to_s.accept_json_serializer v
564 v.stream.write "\}"
565 end
566 end
567 end
568
569 redef class NativeString
570 redef fun accept_json_serializer(v) do to_s.accept_json_serializer(v)
571 end
572
573 redef class Collection[E]
574 # Utility to serialize a normal Json array
575 private fun serialize_to_pure_json(v: JsonSerializer)
576 do
577 v.stream.write "["
578 v.indent_level += 1
579 var is_first = true
580 for e in self do
581 if is_first then
582 is_first = false
583 else v.stream.write ","
584 v.new_line_and_indent
585
586 if not v.try_to_serialize(e) then
587 assert e != null # null would have been serialized
588 v.warn("element of type {e.class_name} is not serializable.")
589 end
590 end
591 v.indent_level -= 1
592 v.new_line_and_indent
593 v.stream.write "]"
594 end
595 end
596
597 redef class SimpleCollection[E]
598 redef fun accept_json_serializer(v)
599 do
600 # Register as pseudo object
601 if not v.plain_json then
602 var id = v.cache.new_id_for(self)
603 v.stream.write """{"""
604 v.indent_level += 1
605 v.new_line_and_indent
606 v.stream.write """"__kind": "obj", "__id": """
607 v.stream.write id.to_s
608 v.stream.write """, "__class": """"
609 v.stream.write class_name
610 v.stream.write """","""
611 v.new_line_and_indent
612 v.stream.write """"__items": """
613 end
614
615 serialize_to_pure_json v
616
617 if not v.plain_json then
618 v.indent_level -= 1
619 v.new_line_and_indent
620 v.stream.write "\}"
621 end
622 end
623
624 redef init from_deserializer(v)
625 do
626 super
627 if v isa JsonDeserializer then
628 v.notify_of_creation self
629 init
630
631 var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
632 for o in arr do
633 var obj = v.convert_object(o)
634 self.add obj
635 end
636 end
637 end
638 end
639
640 redef class Map[K, V]
641 redef fun accept_json_serializer(v)
642 do
643 # Register as pseudo object
644 var id = v.cache.new_id_for(self)
645
646 if v.plain_json then
647 v.stream.write "\{"
648 v.indent_level += 1
649 var first = true
650 for key, val in self do
651 if not first then
652 v.stream.write ","
653 else first = false
654 v.new_line_and_indent
655
656 var k = key or else "null"
657 k.to_s.accept_json_serializer v
658 v.stream.write ": "
659 if not v.try_to_serialize(val) then
660 assert val != null # null would have been serialized
661 v.warn("element of type {val.class_name} is not serializable.")
662 v.stream.write "null"
663 end
664 end
665 v.indent_level -= 1
666 v.new_line_and_indent
667 v.stream.write "\}"
668 else
669 v.stream.write "\{"
670 v.indent_level += 1
671 v.new_line_and_indent
672 v.stream.write """"__kind": "obj", "__id": """
673 v.stream.write id.to_s
674 v.stream.write """, "__class": """"
675 v.stream.write class_name
676 v.stream.write """", "__length": """
677 v.stream.write length.to_s
678
679 v.stream.write ","
680 v.new_line_and_indent
681 v.stream.write """"__keys": """
682 keys.serialize_to_pure_json v
683
684 v.stream.write ","
685 v.new_line_and_indent
686 v.stream.write """"__values": """
687 values.serialize_to_pure_json v
688
689 v.indent_level -= 1
690 v.new_line_and_indent
691 v.stream.write "\}"
692 end
693 end
694
695 redef init from_deserializer(v)
696 do
697 super
698
699 if v isa JsonDeserializer then
700 v.notify_of_creation self
701 init
702
703 var length = v.deserialize_attribute("__length").as(Int)
704 var keys = v.path.last["__keys"].as(SequenceRead[nullable Object])
705 var values = v.path.last["__values"].as(SequenceRead[nullable Object])
706 for i in length.times do
707 var key = v.convert_object(keys[i])
708 var value = v.convert_object(values[i])
709 self[key] = value
710 end
711 end
712 end
713 end