1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Services to write Nit objects to JSON strings: `serialize_to_json` and `JsonSerializer`
16 module serialization_write
18 import ::serialization
::caching
19 private import ::serialization
::engine_tools
21 # Serializer of Nit objects to Json string.
23 super CachingSerializer
25 # Target writing stream
28 # Write plain JSON? Standard JSON without metadata for deserialization
30 # If `false`, the default, serialize to support deserialization:
32 # * Write metadata, including the types of the serialized objects so they can
33 # be deserialized to their original form using `JsonDeserializer`.
34 # * Use references when an object has already been serialized so to not duplicate it.
35 # * Support cycles in references.
36 # * Preserve the Nit `Char` type as an object because it does not exist in JSON.
37 # * The generated JSON is standard and can be read by non-Nit programs.
38 # However, some Nit types are not represented by the simplest possible JSON representation.
39 # With the added metadata, it can be complex to read.
41 # If `true`, serialize for other programs:
43 # * Nit objects are serialized to pure and standard JSON so they can
44 # be easily read by non-Nit programs and humans.
45 # * Nit objects are serialized for every references, so they can be duplicated.
46 # It is easier to read but it creates a larger output.
47 # * Does not support cycles, will replace the problematic references by `null`.
48 # * Does not serialize the metadata needed to deserialize the objects
49 # back to regular Nit objects.
50 # * Keys of Nit `HashMap` are converted to their string representation using `to_s`.
51 var plain_json
= false is writable
53 # Write pretty JSON for human eyes?
55 # Toggles skipping lines between attributes of an object and
56 # properly indent the written JSON.
57 var pretty_json
= false is writable
59 # Current indentation level used for writing `pretty_json`
60 private var indent_level
= 0
62 # List of the current open objects, the first is the main target of the serialization
64 # Used only when `plain_json == true` to detect cycles in serialization.
65 private var open_objects
= new Array[Object]
67 # Has the first attribute of the current object already been serialized?
69 # Used only when `plain_json == true`.
70 private var first_attribute
= false
72 redef var current_object
= null
74 redef fun serialize
(object
)
76 if object
== null then
80 for o
in open_objects
do
81 if object
.is_same_serialized
(o
) then
82 # Cycle, can't be managed in plain json
83 warn
"Cycle detected in serialized object, replacing reference with 'null'."
89 open_objects
.add object
92 first_attribute
= true
93 var last_object
= current_object
94 current_object
= object
95 object
.accept_json_serializer
self
96 first_attribute
= false
97 current_object
= last_object
99 if plain_json
then open_objects
.pop
103 redef fun serialize_attribute
(name
, value
)
105 if not plain_json
or not first_attribute
then
108 first_attribute
= false
114 if pretty_json
then stream
.write
" "
118 redef fun serialize_reference
(object
)
120 if not plain_json
and cache
.has_object
(object
) then
121 # if already serialized, add local reference
122 var id
= cache
.id_for
(object
)
126 stream
.write
"\"__kind\
": \"ref\
", \"__id\
": "
137 # Write a new line and indent it, only if `pretty_json`
138 private fun new_line_and_indent
142 for i
in indent_level
.times
do stream
.write
"\t"
149 redef fun accept_json_serializer
(v
)
155 for i in [0 .. self.length[ do
159 else if char == '\"' then
161 else if char < ' ' then
164 else if char == '\r
' then
166 else if char == '\t
' then
169 escaped = char.escape_to_utf16
173 if escaped != null then
174 # Write open non-escaped string
176 v.stream.write substring(start_i, i-start_i)
179 # Write escaped character
180 v.stream.write escaped
186 # Write remaining non-escaped string
187 if start_i < length then
191 v.stream.write substring(start_i, length-start_i)
199 redef class Serializable
201 # Serialize `self` to JSON
203 # Set `plain = true` to generate standard JSON, without deserialization metadata.
204 # Use this option if the generated JSON will be read by other programs or humans.
205 # Use the default, `plain = false`, if the JSON is to be deserialized by a Nit program.
207 # Set `pretty = true` to generate pretty JSON for human eyes.
208 # Use the default, `pretty = false`, to generate minified JSON.
210 # This method should not be refined by subclasses,
211 # instead `accept_json_serializer` can customize the serialization of an object.
213 # See: `JsonSerializer`
214 fun serialize_to_json(plain, pretty: nullable Bool): String
216 var stream = new StringWriter
217 var serializer = new JsonSerializer(stream)
218 serializer.plain_json = plain or else false
219 serializer.pretty_json = pretty or else false
220 serializer.serialize self
225 # Serialize `self` to plain JSON
227 # Compatibility alias for `serialize_to_json(plain=true)`.
228 fun to_json: String do return serialize_to_json(plain=true)
230 # Serialize `self` to plain pretty JSON
232 # Compatibility alias for `serialize_to_json(plain=true, pretty=true)`.
233 fun to_pretty_json: String do return serialize_to_json(plain=true, pretty=true)
235 # Refinable service to customize the serialization of this class to JSON
237 # This method can be refined to customize the serialization by either
238 # writing pure JSON directly on the stream `v.stream` or
239 # by using other services of `JsonSerializer`.
241 # Most of the time, it is preferable to refine the method `core_serialize_to`
242 # which is used by all the serialization engines, not just JSON.
243 protected fun accept_json_serializer(v: JsonSerializer)
245 var id = v.cache.new_id_for(self)
248 if not v.plain_json then
249 v.new_line_and_indent
250 v.stream.write "\"__kind\": \"obj\", \"__id\": "
251 v.stream.write id.to_s
252 v.stream.write ", \"__class\": \""
253 v.stream.write class_name
256 v.serialize_core(self)
259 v.new_line_and_indent
265 redef fun accept_json_serializer(v) do v.stream.write to_s
269 redef fun accept_json_serializer(v) do v.stream.write to_s
273 redef fun accept_json_serializer(v) do v.stream.write to_s
277 redef fun accept_json_serializer(v)
280 to_s.accept_json_serializer v
282 v.stream.write "\{\"__kind\": \"char\", \"__val\": "
283 to_s.accept_json_serializer v
290 redef fun accept_json_serializer(v) do to_s.accept_json_serializer(v)
293 redef class Collection[E]
294 # Utility to serialize a normal Json array
295 private fun serialize_to_pure_json(v: JsonSerializer)
304 if v.pretty_json then v.stream.write " "
307 if not v.try_to_serialize(e) then
308 assert e != null # null would have been serialized
309 v.warn("element of type {e.class_name} is not serializable.")
310 v.stream.write "null"
317 redef class SimpleCollection[E]
318 redef fun accept_json_serializer(v)
320 # Register as pseudo object
321 if not v.plain_json then
322 var id = v.cache.new_id_for(self)
323 v.stream.write """{"""
325 v.new_line_and_indent
326 v.stream.write """"__kind": "obj", "__id": """
327 v.stream.write id.to_s
328 v.stream.write """, "__class": """"
329 v.stream.write class_name
330 v.stream.write """","""
331 v.new_line_and_indent
332 v.stream.write """"__items": """
333 serialize_to_pure_json v
336 serialize_to_pure_json v
339 if not v.plain_json then
341 v.new_line_and_indent
347 redef class Map[K, V]
348 redef fun accept_json_serializer(v)
350 # Register as pseudo object
351 var id = v.cache.new_id_for(self)
358 for key, val in self do
362 v.new_line_and_indent
364 var k = key or else "null"
365 k.to_s.accept_json_serializer v
367 if v.pretty_json then v.stream.write " "
368 if not v.try_to_serialize(val) then
369 assert val != null # null would have been serialized
370 v.warn("element of type {val.class_name} is not serializable.")
371 v.stream.write "null"
375 v.new_line_and_indent
376 v.stream.write """"__kind": "obj", "__id": """
377 v.stream.write id.to_s
378 v.stream.write """, "__class": """"
379 v.stream.write class_name
380 v.stream.write """", "__length": """
381 v.stream.write length.to_s
384 v.new_line_and_indent
385 v.stream.write """"__keys": """
386 keys.serialize_to_pure_json v
389 v.new_line_and_indent
390 v.stream.write """"__values": """
391 values.serialize_to_pure_json v
397 v.new_line_and_indent