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.
20 import ::serialization
21 private import ::serialization
::engine_tools
24 # Serializer of Nit objects to Json string.
28 # Target writing stream
31 # Write plain JSON? easier to read but does not support Nit deserialization
33 # If `false`, the default, serialize to support deserialization:
35 # * Write meta-data, including the types of the serialized objects so they can
36 # be deserialized to their original form using `JsonDeserializer`.
37 # * Use references when an object has already been serialized so to not duplicate it.
38 # * Support cycles in references.
39 # * Preserve the Nit `Char` type as an object because it does not exist in JSON.
40 # * The generated JSON is standard and can be read by non-Nit programs.
41 # However, some Nit types are not represented by the simplest possible JSON representation.
42 # With the added meta-data, it can be complex to read.
44 # If `true`, serialize for other programs:
46 # * Nit objects are serialized to pure and standard JSON so they can
47 # be easily read by non-Nit programs and humans.
48 # * Nit objects are serialized for every references, so they can be duplicated.
49 # It is easier to read but it creates a larger output.
50 # * Does not support cycles, will replace the problematic references by `null`.
51 # * Does not serialize the meta-data needed to deserialize the objects
52 # back to regular Nit objects.
53 # * Keys of Nit `HashMap` are converted to their string reprensentation using `to_s`.
54 var plain_json
= false is writable
56 # List of the current open objects, the first is the main target of the serialization
58 # Used only when `plain_json == true` to detect cycles in serialization.
59 private var open_objects
= new Array[Object]
61 # Has the first attribute of the current object already been serialized?
63 # Used only when `plain_json == true`.
64 private var first_attribute
= false
66 redef fun serialize
(object
)
68 if object
== null then
72 for o
in open_objects
do
73 if object
.is_same_serialized
(o
) then
80 open_objects
.add object
83 first_attribute
= true
84 object
.serialize_to_json
self
85 first_attribute
= false
87 if plain_json
then open_objects
.pop
91 redef fun serialize_attribute
(name
, value
)
93 if not plain_json
or not first_attribute
then
95 first_attribute
= false
104 redef fun serialize_reference
(object
)
106 if not plain_json
and refs_map
.has_key
(object
) then
107 # if already serialized, add local reference
108 var id
= ref_id_for
(object
)
109 stream
.write
"\{\"__kind\
": \"ref\
", \"__id\
": "
118 # Map of references to already serialized objects.
119 private var refs_map
= new StrictHashMap[Serializable,Int]
121 # Get the internal serialized reference for this `object`.
122 private fun ref_id_for
(object
: Serializable): Int
124 if refs_map
.has_key
(object
) then
125 return refs_map
[object
]
127 var id
= refs_map
.length
128 refs_map
[object
] = id
134 # Deserializer from a Json string.
135 class JsonDeserializer
138 # Json text to deserialize from.
139 private var text
: Text
141 # Root json object parsed from input text.
142 private var root
: nullable Jsonable is noinit
144 # Depth-first path in the serialized object tree.
145 private var path
= new Array[JsonObject]
147 # Map of references to already deserialized objects.
148 private var id_to_object
= new StrictHashMap[Int, Object]
150 # Last encountered object reference id.
152 # See `id_to_object`.
153 var just_opened_id
: nullable Int = null
156 var root
= text
.parse_json
157 if root
isa JsonObject then path
.add
(root
)
161 redef fun deserialize_attribute
(name
)
163 assert not path
.is_empty
164 var current
= path
.last
166 assert current
.keys
.has
(name
)
167 var value
= current
[name
]
169 return convert_object
(value
)
172 # This may be called multiple times by the same object from constructors
173 # in different nclassdef
174 redef fun notify_of_creation
(new_object
)
176 var id
= just_opened_id
177 if id
== null then return # Register `new_object` only once
178 id_to_object
[id
] = new_object
181 # Convert from simple Json object to Nit object
182 private fun convert_object
(object
: nullable Object): nullable Object
184 if object
isa JsonObject then
185 assert object
.keys
.has
("__kind")
186 var kind
= object
["__kind"]
189 if kind
== "ref" then
190 assert object
.keys
.has
("__id")
191 var id
= object
["__id"]
194 assert id_to_object
.has_key
(id
)
195 return id_to_object
[id
]
199 if kind
== "obj" then
200 assert object
.keys
.has
("__id")
201 var id
= object
["__id"]
204 assert object
.keys
.has
("__class")
205 var class_name
= object
["__class"]
206 assert class_name
isa String
208 assert not id_to_object
.has_key
(id
) else print
"Error: Object with id '{id}' of {class_name} is deserialized twice."
214 var value
= deserialize_class
(class_name
)
215 just_opened_id
= null
224 if kind
== "char" then
225 assert object
.keys
.has
("__val")
226 var val
= object
["__val"]
227 assert val
isa String
229 if val
.length
!= 1 then print
"Error: expected a single char when deserializing '{val}'."
231 return val
.chars
.first
234 print
"Malformed Json string: unexpected Json Object kind '{kind or else "null"}'"
238 if object
isa Array[nullable Object] then
239 # special case, isa Array[nullable Serializable]
240 var array
= new Array[nullable Serializable]
241 for e
in object
do array
.add e
.as(nullable Serializable)
248 redef fun deserialize
do return convert_object
(root
)
251 redef class Serializable
252 private fun serialize_to_json
(v
: JsonSerializer)
254 var id
= v
.ref_id_for
(self)
256 if not v
.plain_json
then
257 v
.stream
.write
"\"__kind\
": \"obj\
", \"__id\
": "
258 v
.stream
.write id
.to_s
259 v
.stream
.write
", \"__class\
": \""
260 v.stream.write class_name
267 # Serialize this object to plain JSON
269 # This is a shortcut using `JsonSerializer::plain_json`,
270 # see its documentation for more information.
271 fun to_plain_json
: String
273 var stream
= new StringWriter
274 var serializer
= new JsonSerializer(stream
)
275 serializer
.plain_json
= true
276 serializer
.serialize
self
283 redef fun serialize_to_json
(v
) do v
.stream
.write
(to_s
)
287 redef fun serialize_to_json
(v
) do v
.stream
.write
(to_s
)
291 redef fun serialize_to_json
(v
) do v
.stream
.write
(to_s
)
295 redef fun serialize_to_json
(v
)
298 v
.stream
.write to_s
.to_json
300 v
.stream
.write
"\{\"__kind\
": \"char\
", \"__val\
": "
301 v
.stream
.write to_s
.to_json
308 redef fun serialize_to_json
(v
) do v
.stream
.write
(to_json
)
311 redef class NativeString
312 redef fun serialize_to_json
(v
) do to_s
.serialize_to_json
(v
)
315 redef class Collection[E
]
316 # Utility to serialize a normal Json array
317 private fun serialize_to_pure_json
(v
: JsonSerializer)
324 else v
.stream
.write
", "
326 if not v
.try_to_serialize
(e
) then
327 v
.warn
("element of type {e.class_name} is not serializable.")
334 redef class SimpleCollection[E
]
335 redef fun serialize_to_json
(v
)
337 # Register as pseudo object
338 if not v
.plain_json
then
339 var id
= v
.ref_id_for
(self)
340 v
.stream
.write
"""{"__kind": "obj", "__id": """
341 v
.stream
.write id
.to_s
342 v
.stream
.write
""", "__class": """"
343 v.stream.write class_name
344 v.stream.write """", "__length": """
345 v.stream.write length.to_s
346 v.stream.write """, "__items": """
349 serialize_to_pure_json v
351 if not v.plain_json then
356 redef init from_deserializer(v: Deserializer)
358 if v isa JsonDeserializer then
359 v.notify_of_creation self
362 var length = v.deserialize_attribute("__length").as(Int)
363 var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
364 for i in length.times do
365 var obj = v.convert_object(arr[i])
373 redef fun serialize_to_json(v)
375 if v.plain_json or class_name == "Array[nullable Serializable]" then
376 # Using class_name to get the exact type,
377 # we do not want Array[Int] or anything else here.
379 serialize_to_pure_json v
384 redef class Map[K, V]
385 redef fun serialize_to_json(v)
387 # Register as pseudo object
388 var id = v.ref_id_for(self)
393 for key, val in self do
398 if key == null then key = "null"
400 v.stream.write key.to_s.to_json
402 if not v.try_to_serialize(val) then
403 v.warn("element of type {val.class_name} is not serializable.")
404 v.stream.write "null"
409 v.stream.write """{"__kind": "obj", "__id": """
410 v.stream.write id.to_s
411 v.stream.write """, "__class": """"
412 v.stream.write class_name
413 v.stream.write """", "__length
": """
414 v.stream.write length.to_s
416 v.stream.write """, "__keys
": """
417 keys.serialize_to_pure_json v
419 v.stream.write """, "__values
": """
420 values.serialize_to_pure_json v
426 # Instantiate a new `Array` from its serialized representation.
427 redef init from_deserializer(v: Deserializer)
431 if v isa JsonDeserializer then
432 v.notify_of_creation self
434 var length = v.deserialize_attribute("__length
").as(Int)
435 var keys = v.path.last["__keys
"].as(SequenceRead[nullable Object])
436 var values = v.path.last["__values
"].as(SequenceRead[nullable Object])
437 for i in length.times do
438 var key = v.convert_object(keys[i])
439 var value = v.convert_object(values[i])