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