1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # Handles serialization and deserialization of objects to/from JSON
19 # ## Writing JSON with metadata
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.
25 # For more information on Nit serialization, see: ../serialization/README.md
27 # ## Writing plain JSON
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`.
34 # A shortcut to these writing services is provided by `Serializable::serialize_to_json`.
39 # import json::serialization
45 # var year_of_birth: Int
46 # var next_of_kin: nullable Person
49 # var bob = new Person("Bob", 1986)
50 # var alice = new Person("Alice", 1978, bob)
52 # assert bob.serialize_to_json(pretty=true, plain=true) == """
55 # "year_of_birth": 1986,
59 # assert alice.serialize_to_json(pretty=true, plain=true) == """
62 # "year_of_birth": 1978,
65 # "year_of_birth": 1986,
71 # ## JSON to Nit objects
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.
81 # import json::serialization
86 # var description: String
87 # var max_participants: nullable Int
88 # var answers: Array[FlatString]
92 # {"__class": "MeetupConfig", "description": "My Awesome Meetup", "max_participants": null, "answers": ["Pepperoni", "Chicken"]}"""
93 # var deserializer = new JsonDeserializer(json_code)
95 # var meet = deserializer.deserialize
98 # assert deserializer.errors.is_empty
100 # assert meet isa MeetupConfig
101 # assert meet.description == "My Awesome Meetup"
102 # assert meet.max_participants == null
103 # assert meet.answers == ["Pepperoni", "Chicken"]
107 import ::serialization
::caching
108 private import ::serialization
::engine_tools
109 private import static
111 # Serializer of Nit objects to Json string.
113 super CachingSerializer
115 # Target writing stream
118 # Write plain JSON? Standard JSON without metadata for deserialization
120 # If `false`, the default, serialize to support deserialization:
122 # * Write metadata, including the types of the serialized objects so they can
123 # be deserialized to their original form using `JsonDeserializer`.
124 # * Use references when an object has already been serialized so to not duplicate it.
125 # * Support cycles in references.
126 # * Preserve the Nit `Char` type as an object because it does not exist in JSON.
127 # * The generated JSON is standard and can be read by non-Nit programs.
128 # However, some Nit types are not represented by the simplest possible JSON representation.
129 # With the added metadata, it can be complex to read.
131 # If `true`, serialize for other programs:
133 # * Nit objects are serialized to pure and standard JSON so they can
134 # be easily read by non-Nit programs and humans.
135 # * Nit objects are serialized for every references, so they can be duplicated.
136 # It is easier to read but it creates a larger output.
137 # * Does not support cycles, will replace the problematic references by `null`.
138 # * Does not serialize the metadata needed to deserialize the objects
139 # back to regular Nit objects.
140 # * Keys of Nit `HashMap` are converted to their string representation using `to_s`.
141 var plain_json
= false is writable
143 # Write pretty JSON for human eyes?
145 # Toggles skipping lines between attributes of an object and
146 # properly indent the written JSON.
147 var pretty_json
= false is writable
149 # Current indentation level used for writing `pretty_json`
150 private var indent_level
= 0
152 # List of the current open objects, the first is the main target of the serialization
154 # Used only when `plain_json == true` to detect cycles in serialization.
155 private var open_objects
= new Array[Object]
157 # Has the first attribute of the current object already been serialized?
159 # Used only when `plain_json == true`.
160 private var first_attribute
= false
162 redef fun serialize
(object
)
164 if object
== null then
168 for o
in open_objects
do
169 if object
.is_same_serialized
(o
) then
170 # Cycle, can't be managed in plain json
171 warn
"Cycle detected in serialized object, replacing reference with 'null'."
177 open_objects
.add object
180 first_attribute
= true
181 object
.accept_json_serializer
self
182 first_attribute
= false
184 if plain_json
then open_objects
.pop
188 redef fun serialize_attribute
(name
, value
)
190 if not plain_json
or not first_attribute
then
192 first_attribute
= false
202 redef fun serialize_reference
(object
)
204 if not plain_json
and cache
.has_object
(object
) then
205 # if already serialized, add local reference
206 var id
= cache
.id_for
(object
)
210 stream
.write
"\"__kind\
": \"ref\
", \"__id\
": "
221 # Write a new line and indent it, only if `pretty_json`
222 private fun new_line_and_indent
226 for i
in indent_level
.times
do stream
.write
"\t"
231 # Deserializer from a Json string.
232 class JsonDeserializer
233 super CachingDeserializer
235 # Json text to deserialize from.
236 private var text
: Text
238 # Root json object parsed from input text.
239 private var root
: nullable Object is noinit
241 # Depth-first path in the serialized object tree.
242 private var path
= new Array[Map[String, nullable Object]]
244 # Last encountered object reference id.
246 # See `id_to_object`.
247 var just_opened_id
: nullable Int = null
250 var root
= text
.parse_json
251 if root
isa Map[String, nullable Object] then path
.add
(root
)
255 redef fun deserialize_attribute
(name
)
257 assert not path
.is_empty
# This is an internal error, abort
258 var current
= path
.last
260 if not current
.keys
.has
(name
) then
261 errors
.add
new Error("Deserialization Error: JSON object has not attribute '{name}'.")
265 var value
= current
[name
]
267 return convert_object
(value
)
270 # This may be called multiple times by the same object from constructors
271 # in different nclassdef
272 redef fun notify_of_creation
(new_object
)
274 var id
= just_opened_id
275 if id
== null then return # Register `new_object` only once
276 cache
[id
] = new_object
279 # Convert from simple Json object to Nit object
280 private fun convert_object
(object
: nullable Object): nullable Object
282 if object
isa JsonParseError then
287 if object
isa Map[String, nullable Object] then
289 if object
.keys
.has
("__kind") then
290 kind
= object
["__kind"]
294 if kind
== "ref" then
295 if not object
.keys
.has
("__id") then
296 errors
.add
new Error("Serialization Error: JSON object reference does not declare a `__id`.")
300 var id
= object
["__id"]
301 if not id
isa Int then
302 errors
.add
new Error("Serialization Error: JSON object reference declares a non-integer `__id`.")
306 if not cache
.has_id
(id
) then
307 errors
.add
new Error("Serialization Error: JSON object reference has an unknown `__id`.")
311 return cache
.object_for
(id
)
315 if kind
== "obj" or kind
== null then
317 if object
.keys
.has
("__id") then
320 if not id
isa Int then
321 errors
.add
new Error("Serialization Error: JSON object declaration declares a non-integer `__id`.")
325 if cache
.has_id
(id
) then
326 errors
.add
new Error("Serialization Error: JSON object with `__id` {id} is deserialized twice.")
331 var class_name
= object
.get_or_null
("__class")
332 if class_name
== null then
333 # Fallback to custom heuristic
334 class_name
= class_name_heuristic
(object
)
337 if class_name
== null then
338 errors
.add
new Error("Serialization Error: JSON object declaration does not declare a `__class`.")
342 if not class_name
isa String then
343 errors
.add
new Error("Serialization Error: JSON object declaration declares a non-string `__class`.")
351 var value
= deserialize_class
(class_name
)
352 just_opened_id
= null
361 if kind
== "char" then
362 if not object
.keys
.has
("__val") then
363 errors
.add
new Error("Serialization Error: JSON `char` object does not declare a `__val`.")
367 var val
= object
["__val"]
369 if not val
isa String or val
.is_empty
then
370 errors
.add
new Error("Serialization Error: JSON `char` object does not declare a single char in `__val`.")
374 return val
.chars
.first
377 errors
.add
new Error("Serialization Error: JSON object has an unknown `__kind`.")
381 # Simple JSON array without serialization metadata
382 if object
isa Array[nullable Object] then
383 var array
= new Array[nullable Object]
384 var types
= new HashSet[String]
385 var has_nullable
= false
387 var res
= convert_object
(e
)
391 types
.add res
.class_name
392 else has_nullable
= true
395 if types
.length
== 1 then
396 var array_type
= types
.first
399 if array_type
== "ASCIIFlatString" or array_type
== "UnicodeFlatString" then
401 typed_array
= new Array[nullable FlatString]
402 else typed_array
= new Array[FlatString]
403 else if array_type
== "Int" then
405 typed_array
= new Array[nullable Int]
406 else typed_array
= new Array[Int]
407 else if array_type
== "Float" then
409 typed_array
= new Array[nullable Float]
410 else typed_array
= new Array[Float]
412 # TODO support all array types when we separate the constructor
413 # `from_deserializer` from the filling of the items.
415 if not has_nullable
then
416 typed_array
= new Array[Object]
418 # Unsupported array type, return as `Array[nullable Object]`
423 assert typed_array
isa Array[nullable Object]
425 # Copy item to the new array
426 for e
in array
do typed_array
.add e
430 # Uninferable type, return as `Array[nullable Object]`
437 redef fun deserialize
440 return convert_object
(root
)
443 # User customizable heuristic to get the name of the Nit class to deserialize `json_object`
445 # This method is called only when deserializing an object without the metadata `__class`.
446 # Return the class name as a `String` when it can be inferred.
447 # Return `null` when the class name cannot be found.
449 # If a valid class name is returned, `json_object` will then be deserialized normally.
450 # So it must contain the attributes of the corresponding class, as usual.
465 # class MyJsonDeserializer
466 # super JsonDeserializer
468 # redef fun class_name_heuristic(json_object)
470 # if json_object.keys.has("error") then return "MyError"
471 # if json_object.keys.has("data") then return "MyData"
476 # var json = """{"data": "some other data"}"""
477 # var deserializer = new MyJsonDeserializer(json)
478 # var deserialized = deserializer.deserialize
479 # assert deserialized isa MyData
481 # json = """{"error": "some error message"}"""
482 # deserializer = new MyJsonDeserializer(json)
483 # deserialized = deserializer.deserialize
484 # assert deserialized isa MyError
486 protected fun class_name_heuristic
(json_object
: Map[String, nullable Object]): nullable String
494 # Deserialize a `nullable Object` from this JSON formatted string
496 # Warning: Deserialization errors are reported with `print_error` and
497 # may be returned as a partial object or as `null`.
499 # This method is not appropriate when errors need to be handled programmatically,
500 # manually use a `JsonDeserializer` in such cases.
501 fun from_json_string
: nullable Object
503 var deserializer
= new JsonDeserializer(self)
504 var res
= deserializer
.deserialize
505 if deserializer
.errors
.not_empty
then
506 print_error
"Deserialization Errors: {deserializer.errors.join(", ")}"
511 redef fun accept_json_serializer
(v
) do v
.stream
.write
(to_json
)
514 redef class Serializable
516 # Serialize `self` to JSON
518 # Set `plain = true` to generate standard JSON, without deserialization metadata.
519 # Use this option if the generated JSON will be read by other programs or humans.
520 # Use the default, `plain = false`, if the JSON is to be deserialized by a Nit program.
522 # Set `pretty = true` to generate pretty JSON for human eyes.
523 # Use the default, `pretty = false`, to generate minified JSON.
525 # This method should not be refined by subclasses,
526 # instead `accept_json_serializer` can customize the serialization of an object.
528 # See: `JsonSerializer`
529 fun serialize_to_json
(plain
, pretty
: nullable Bool): String
531 var stream
= new StringWriter
532 var serializer
= new JsonSerializer(stream
)
533 serializer
.plain_json
= plain
or else false
534 serializer
.pretty_json
= pretty
or else false
535 serializer
.serialize
self
540 # Refinable service to customize the serialization of this class to JSON
542 # This method can be refined to customize the serialization by either
543 # writing pure JSON directly on the stream `v.stream` or
544 # by using other services of `JsonSerializer`.
546 # Most of the time, it is preferable to refine the method `core_serialize_to`
547 # which is used by all the serialization engines, not just JSON.
548 protected fun accept_json_serializer
(v
: JsonSerializer)
550 var id
= v
.cache
.new_id_for
(self)
553 if not v
.plain_json
then
554 v
.new_line_and_indent
555 v
.stream
.write
"\"__kind\
": \"obj\
", \"__id\
": "
556 v
.stream
.write id
.to_s
557 v
.stream
.write
", \"__class\
": \""
558 v.stream.write class_name
564 v
.new_line_and_indent
570 redef fun accept_json_serializer
(v
) do v
.stream
.write to_s
574 redef fun accept_json_serializer
(v
) do v
.stream
.write to_s
578 redef fun accept_json_serializer
(v
) do v
.stream
.write to_s
582 redef fun accept_json_serializer
(v
)
585 to_s
.accept_json_serializer v
587 v
.stream
.write
"\{\"__kind\
": \"char\
", \"__val\
": "
588 to_s
.accept_json_serializer v
594 redef class NativeString
595 redef fun accept_json_serializer
(v
) do to_s
.accept_json_serializer
(v
)
598 redef class Collection[E
]
599 # Utility to serialize a normal Json array
600 private fun serialize_to_pure_json
(v
: JsonSerializer)
608 else v
.stream
.write
","
609 v
.new_line_and_indent
611 if not v
.try_to_serialize
(e
) then
612 assert e
!= null # null would have been serialized
613 v
.warn
("element of type {e.class_name} is not serializable.")
617 v
.new_line_and_indent
622 redef class SimpleCollection[E
]
623 redef fun accept_json_serializer
(v
)
625 # Register as pseudo object
626 if not v
.plain_json
then
627 var id
= v
.cache
.new_id_for
(self)
628 v
.stream
.write
"""{"""
630 v
.new_line_and_indent
631 v
.stream
.write
""""__kind": "obj", "__id": """
632 v
.stream
.write id
.to_s
633 v
.stream
.write
""", "__class": """"
634 v.stream.write class_name
635 v.stream.write """","""
636 v.new_line_and_indent
637 v.stream.write """"__items": """
640 serialize_to_pure_json v
642 if not v.plain_json then
644 v.new_line_and_indent
649 redef init from_deserializer(v)
652 if v isa JsonDeserializer then
653 v.notify_of_creation self
656 var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
658 var obj = v.convert_object(o)
665 redef class Map[K, V]
666 redef fun accept_json_serializer(v)
668 # Register as pseudo object
669 var id = v.cache.new_id_for(self)
675 for key, val in self do
679 v.new_line_and_indent
681 var k = key or else "null"
682 k.to_s.accept_json_serializer v
684 if not v.try_to_serialize(val) then
685 assert val != null # null would have been serialized
686 v.warn("element of type {val.class_name} is not serializable.")
687 v.stream.write "null"
691 v.new_line_and_indent
696 v.new_line_and_indent
697 v.stream.write """"__kind": "obj", "__id": """
698 v.stream.write id.to_s
699 v.stream.write """, "__class": """"
700 v.stream.write class_name
701 v.stream.write """", "__length
": """
702 v.stream.write length.to_s
705 v.new_line_and_indent
706 v.stream.write """"__keys
": """
707 keys.serialize_to_pure_json v
710 v.new_line_and_indent
711 v.stream.write """"__values
": """
712 values.serialize_to_pure_json v
715 v.new_line_and_indent
720 redef init from_deserializer(v)
724 if v isa JsonDeserializer then
725 v.notify_of_creation self
728 var length = v.deserialize_attribute("__length
").as(Int)
729 var keys = v.path.last["__keys
"].as(SequenceRead[nullable Object])
730 var values = v.path.last["__values
"].as(SequenceRead[nullable Object])
731 for i in length.times do
732 var key = v.convert_object(keys[i])
733 var value = v.convert_object(values[i])