lib/json: adds `to_plain_json`
[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 module serialization
19
20 import ::serialization
21 private import ::serialization::engine_tools
22 private import static
23
24 # Serializer of Nit objects to Json string.
25 class JsonSerializer
26 super Serializer
27
28 # Target writing stream
29 var stream: Writer
30
31 # Write plain JSON? easier to read but does not support Nit deserialization
32 #
33 # If `false`, the default, serialize to support deserialization:
34 #
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.
43 #
44 # If `true`, serialize for other programs:
45 #
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
55
56 # List of the current open objects, the first is the main target of the serialization
57 #
58 # Used only when `plain_json == true` to detect cycles in serialization.
59 private var open_objects = new Array[Object]
60
61 # Has the first attribute of the current object already been serialized?
62 #
63 # Used only when `plain_json == true`.
64 private var first_attribute = false
65
66 redef fun serialize(object)
67 do
68 if object == null then
69 stream.write "null"
70 else
71 if plain_json then
72 for o in open_objects do
73 if object.is_same_serialized(o) then
74 # Cycle detected
75 stream.write "null"
76 return
77 end
78 end
79
80 open_objects.add object
81 end
82
83 first_attribute = true
84 object.serialize_to_json self
85 first_attribute = false
86
87 if plain_json then open_objects.pop
88 end
89 end
90
91 redef fun serialize_attribute(name, value)
92 do
93 if not plain_json or not first_attribute then
94 stream.write ", "
95 first_attribute = false
96 end
97
98 stream.write "\""
99 stream.write name
100 stream.write "\": "
101 super
102 end
103
104 redef fun serialize_reference(object)
105 do
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\": "
110 stream.write id.to_s
111 stream.write "\}"
112 else
113 # serialize here
114 serialize object
115 end
116 end
117
118 # Map of references to already serialized objects.
119 private var refs_map = new StrictHashMap[Serializable,Int]
120
121 # Get the internal serialized reference for this `object`.
122 private fun ref_id_for(object: Serializable): Int
123 do
124 if refs_map.has_key(object) then
125 return refs_map[object]
126 else
127 var id = refs_map.length
128 refs_map[object] = id
129 return id
130 end
131 end
132 end
133
134 # Deserializer from a Json string.
135 class JsonDeserializer
136 super Deserializer
137
138 # Json text to deserialize from.
139 private var text: Text
140
141 # Root json object parsed from input text.
142 private var root: nullable Jsonable is noinit
143
144 # Depth-first path in the serialized object tree.
145 private var path = new Array[JsonObject]
146
147 # Map of references to already deserialized objects.
148 private var id_to_object = new StrictHashMap[Int, Object]
149
150 # Last encountered object reference id.
151 #
152 # See `id_to_object`.
153 var just_opened_id: nullable Int = null
154
155 init do
156 var root = text.parse_json
157 if root isa JsonObject then path.add(root)
158 self.root = root
159 end
160
161 redef fun deserialize_attribute(name)
162 do
163 assert not path.is_empty
164 var current = path.last
165
166 assert current.keys.has(name)
167 var value = current[name]
168
169 return convert_object(value)
170 end
171
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)
175 do
176 var id = just_opened_id
177 if id == null then return # Register `new_object` only once
178 id_to_object[id] = new_object
179 end
180
181 # Convert from simple Json object to Nit object
182 private fun convert_object(object: nullable Object): nullable Object
183 do
184 if object isa JsonObject then
185 assert object.keys.has("__kind")
186 var kind = object["__kind"]
187
188 # ref?
189 if kind == "ref" then
190 assert object.keys.has("__id")
191 var id = object["__id"]
192 assert id isa Int
193
194 assert id_to_object.has_key(id)
195 return id_to_object[id]
196 end
197
198 # obj?
199 if kind == "obj" then
200 assert object.keys.has("__id")
201 var id = object["__id"]
202 assert id isa Int
203
204 assert object.keys.has("__class")
205 var class_name = object["__class"]
206 assert class_name isa String
207
208 assert not id_to_object.has_key(id) else print "Error: Object with id '{id}' of {class_name} is deserialized twice."
209
210 # advance on path
211 path.push object
212
213 just_opened_id = id
214 var value = deserialize_class(class_name)
215 just_opened_id = null
216
217 # revert on path
218 path.pop
219
220 return value
221 end
222
223 # char?
224 if kind == "char" then
225 assert object.keys.has("__val")
226 var val = object["__val"]
227 assert val isa String
228
229 if val.length != 1 then print "Error: expected a single char when deserializing '{val}'."
230
231 return val.chars.first
232 end
233
234 print "Malformed Json string: unexpected Json Object kind '{kind or else "null"}'"
235 abort
236 end
237
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)
242 return array
243 end
244
245 return object
246 end
247
248 redef fun deserialize do return convert_object(root)
249 end
250
251 redef class Serializable
252 private fun serialize_to_json(v: JsonSerializer)
253 do
254 var id = v.ref_id_for(self)
255 v.stream.write "\{"
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
261 v.stream.write "\""
262 end
263 core_serialize_to(v)
264 v.stream.write "\}"
265 end
266
267 # Serialize this object to plain JSON
268 #
269 # This is a shortcut using `JsonSerializer::plain_json`,
270 # see its documentation for more information.
271 fun to_plain_json: String
272 do
273 var stream = new StringWriter
274 var serializer = new JsonSerializer(stream)
275 serializer.plain_json = true
276 serializer.serialize self
277 stream.close
278 return stream.to_s
279 end
280 end
281
282 redef class Int
283 redef fun serialize_to_json(v) do v.stream.write(to_s)
284 end
285
286 redef class Float
287 redef fun serialize_to_json(v) do v.stream.write(to_s)
288 end
289
290 redef class Bool
291 redef fun serialize_to_json(v) do v.stream.write(to_s)
292 end
293
294 redef class Char
295 redef fun serialize_to_json(v)
296 do
297 if v.plain_json then
298 v.stream.write to_s.to_json
299 else
300 v.stream.write "\{\"__kind\": \"char\", \"__val\": "
301 v.stream.write to_s.to_json
302 v.stream.write "\}"
303 end
304 end
305 end
306
307 redef class String
308 redef fun serialize_to_json(v) do v.stream.write(to_json)
309 end
310
311 redef class NativeString
312 redef fun serialize_to_json(v) do to_s.serialize_to_json(v)
313 end
314
315 redef class Collection[E]
316 # Utility to serialize a normal Json array
317 private fun serialize_to_pure_json(v: JsonSerializer)
318 do
319 v.stream.write "["
320 var is_first = true
321 for e in self do
322 if is_first then
323 is_first = false
324 else v.stream.write ", "
325
326 if not v.try_to_serialize(e) then
327 v.warn("element of type {e.class_name} is not serializable.")
328 end
329 end
330 v.stream.write "]"
331 end
332 end
333
334 redef class SimpleCollection[E]
335 redef fun serialize_to_json(v)
336 do
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": """
347 end
348
349 serialize_to_pure_json v
350
351 if not v.plain_json then
352 v.stream.write "\}"
353 end
354 end
355
356 redef init from_deserializer(v: Deserializer)
357 do
358 if v isa JsonDeserializer then
359 v.notify_of_creation self
360 init
361
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])
366 self.add obj
367 end
368 end
369 end
370 end
371
372 redef class Array[E]
373 redef fun serialize_to_json(v)
374 do
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.
378
379 serialize_to_pure_json v
380 else super
381 end
382 end
383
384 redef class Map[K, V]
385 redef fun serialize_to_json(v)
386 do
387 # Register as pseudo object
388 var id = v.ref_id_for(self)
389
390 if v.plain_json then
391 v.stream.write "\{"
392 var first = true
393 for key, val in self do
394 if not first then
395 v.stream.write ", "
396 else first = false
397
398 if key == null then key = "null"
399
400 v.stream.write key.to_s.to_json
401 v.stream.write ": "
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"
405 end
406 end
407 v.stream.write "\}"
408 else
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
415
416 v.stream.write """, "__keys": """
417 keys.serialize_to_pure_json v
418
419 v.stream.write """, "__values": """
420 values.serialize_to_pure_json v
421
422 v.stream.write "\}"
423 end
424 end
425
426 # Instantiate a new `Array` from its serialized representation.
427 redef init from_deserializer(v: Deserializer)
428 do
429 init
430
431 if v isa JsonDeserializer then
432 v.notify_of_creation self
433
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])
440 self[key] = value
441 end
442 end
443 end
444 end