lib/json_serialization: clean up
[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 json_serialization
19
20 import serialization
21 import json::static
22
23 # Serializer of Nit objects to Json string.
24 class JsonSerializer
25 super Serializer
26
27 # Target writing stream
28 var stream: Writer
29
30 redef fun serialize(object)
31 do
32 if object == null then
33 stream.write "null"
34 else object.serialize_to_json(self)
35 end
36
37 redef fun serialize_attribute(name, value)
38 do
39 stream.write ", \"{name}\": "
40 super
41 end
42
43 redef fun serialize_reference(object)
44 do
45 if refs_map.keys.has(object) then
46 # if already serialized, add local reference
47 var id = ref_id_for(object)
48 stream.write "\{\"__kind\": \"ref\", \"__id\": {id}\}"
49 else
50 # serialize here
51 serialize object
52 end
53 end
54
55 # Map of references to already serialized objects.
56 var refs_map = new HashMap[Serializable,Int]
57
58 # Get the internal serialized reference for this `object`.
59 private fun ref_id_for(object: Serializable): Int
60 do
61 if refs_map.keys.has(object) then
62 return refs_map[object]
63 else
64 var id = refs_map.length
65 refs_map[object] = id
66 return id
67 end
68 end
69 end
70
71 # Deserializer from a Json string.
72 class JsonDeserializer
73 super Deserializer
74
75 # Json text to deserialize from.
76 private var text: Text
77
78 # Root json object parsed from input text.
79 var root: nullable Jsonable is noinit
80
81 # Depth-first path in the serialized object tree.
82 var path = new Array[JsonObject]
83
84 # Map of refenrences to already deserialized objects.
85 var id_to_object = new HashMap[Int, Object]
86
87 # Last encountered object reference id.
88 #
89 # See `id_to_object`.
90 var just_opened_id: nullable Int = null
91
92 init do
93 var root = text.parse_json
94 if root isa JsonObject then path.add(root)
95 self.root = root
96 end
97
98 redef fun deserialize_attribute(name)
99 do
100 assert not path.is_empty
101 var current = path.last
102
103 assert current.keys.has(name)
104 var value = current[name]
105
106 return convert_object(value)
107 end
108
109 # This may be called multiple times by the same object from constructors
110 # in different nclassdef
111 redef fun notify_of_creation(new_object)
112 do
113 var id = just_opened_id
114 assert id != null
115 id_to_object[id] = new_object
116 end
117
118 # Convert from simple Json object to Nit object
119 private fun convert_object(object: nullable Object): nullable Object
120 do
121 if object isa JsonObject then
122 assert object.keys.has("__kind")
123 var kind = object["__kind"]
124
125 # ref?
126 if kind == "ref" then
127 assert object.keys.has("__id")
128 var id = object["__id"]
129 assert id isa Int
130
131 assert id_to_object.keys.has(id)
132 return id_to_object[id]
133 end
134
135 # obj?
136 if kind == "obj" then
137 assert object.keys.has("__id")
138 var id = object["__id"]
139 assert id isa Int
140
141 assert object.keys.has("__class")
142 var class_name = object["__class"]
143 assert class_name isa String
144
145 assert not id_to_object.keys.has(id) else print "Error: Object with id '{id}' is deserialized twice."
146
147 # advance on path
148 path.push object
149
150 just_opened_id = id
151 var value = deserialize_class(class_name)
152 just_opened_id = null
153
154 # revert on path
155 path.pop
156
157 return value
158 end
159
160 # char?
161 if kind == "char" then
162 assert object.keys.has("__val")
163 var val = object["__val"]
164 assert val isa String
165
166 if val.length != 1 then print "Error: expected a single char when deserializing '{val}'."
167
168 return val.chars.first
169 end
170
171 print "Malformed Json string: unexpected Json Object kind '{kind or else "null"}'"
172 abort
173 end
174
175 if object isa Array[nullable Object] then
176 # special case, isa Array[nullable Serializable]
177 var array = new Array[nullable Serializable]
178 for e in object do array.add e.as(nullable Serializable)
179 return array
180 end
181
182 return object
183 end
184
185 redef fun deserialize do return convert_object(root)
186 end
187
188 redef class Serializable
189 private fun serialize_to_json(v: JsonSerializer)
190 do
191 var id = v.ref_id_for(self)
192 v.stream.write "\{\"__kind\": \"obj\", \"__id\": {id}, \"__class\": \"{class_name}\""
193 core_serialize_to(v)
194 v.stream.write "\}"
195 end
196 end
197
198 redef class Int
199 redef fun serialize_to_json(v) do v.stream.write(to_s)
200 end
201
202 redef class Float
203 redef fun serialize_to_json(v) do v.stream.write(to_s)
204 end
205
206 redef class Bool
207 redef fun serialize_to_json(v) do v.stream.write(to_s)
208 end
209
210 redef class Char
211 redef fun serialize_to_json(v) do v.stream.write "\{\"__kind\": \"char\", \"__val\": {to_s.to_json}\}"
212 end
213
214 redef class String
215 redef fun serialize_to_json(v) do v.stream.write(to_json)
216 end
217
218 redef class NativeString
219 redef fun serialize_to_json(v) do to_s.serialize_to_json(v)
220 end
221
222 redef class Array[E]
223 redef fun serialize_to_json(v)
224 do
225 if class_name == "Array[nullable Serializable]" then
226 # Using class_name to the the exact type
227 # We do not want Array[Int] or anything else here
228 v.stream.write "["
229 var is_first = true
230 for e in self do
231 if is_first then
232 is_first = false
233 else v.stream.write(", ")
234
235 if not v.try_to_serialize(e) then
236 v.warn("element of type {e.class_name} is not serializable.")
237 end
238 end
239 v.stream.write "]"
240 else
241 # Register as pseudo object
242 var id = v.ref_id_for(self)
243 v.stream.write "\{\"__kind\": \"obj\", \"__id\": {id}, \"__class\": \"{class_name}\""
244 v.stream.write """, "__length": {{{length}}}, "__items": ["""
245 var is_first = true
246 for e in self do
247 if is_first then
248 is_first = false
249 else v.stream.write(", ")
250
251 if not v.try_to_serialize(e) then
252 v.warn("element of type {e.class_name} is not serializable.")
253 end
254 end
255 v.stream.write "]"
256 v.stream.write "\}"
257 end
258 end
259
260 # Instanciate a new `Array` from its serialized representation.
261 init from_deserializer(v: Deserializer)
262 do
263 if v isa JsonDeserializer then
264 v.notify_of_creation self
265
266 var length = v.deserialize_attribute("__length").as(Int)
267 var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
268 for i in length.times do
269 var obj = v.convert_object(arr[i])
270 self.add obj
271 end
272 end
273 end
274 end