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