phases: add support for deserialization
[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 simple_json_reader
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 Object
75 var path = new Array[HashMap[String, nullable Object]]
76 var id_to_object = new HashMap[Int, Object]
77
78 var just_opened_id: nullable Int = null
79
80 init(text: String)
81 do
82 var root = text.json_to_nit_object
83 if root isa HashMap[String, nullable Object] 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 HashMap[String, nullable Object] 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? TODO
150
151 print "Malformed Json string: unexpected Json Object kind '{kind}'"
152 abort
153 end
154
155 return object
156 end
157
158 redef fun deserialize do return convert_object(root)
159 end
160
161 redef class Serializable
162 private fun serialize_to_json(v: JsonSerializer)
163 do
164 var id = v.ref_id_for(self)
165 v.stream.write "\{\"__kind\": \"obj\", \"__id\": {id}, \"__class\": \"{class_name}\""
166 core_serialize_to(v)
167 v.stream.write "\}"
168 end
169 end
170
171 redef class Int
172 redef fun serialize_to_json(v) do v.stream.write(to_s)
173 end
174
175 redef class Float
176 redef fun serialize_to_json(v) do v.stream.write(to_s)
177 end
178
179 redef class Bool
180 redef fun serialize_to_json(v) do v.stream.write(to_s)
181 end
182
183 redef class Char
184 redef fun serialize_to_json(v) do v.stream.write("'{to_s}'")
185 end
186
187 redef class String
188 redef fun serialize_to_json(v) do v.stream.write("\"{to_json_s}\"")
189
190 private fun to_json_s: String do return self.replace("\\", "\\\\").
191 replace("\"", "\\\"").replace("\b", "\\b").replace("/", "\\/").
192 replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
193 # FIXME add support for unicode char when supported by Nit strings
194 # FIXME add support for \f! # .replace("\f", "\\f")
195 end
196
197 redef class NativeString
198 redef fun serialize_to_json(v) do to_s.serialize_to_json(v)
199 end
200
201 redef class Array[E]
202 redef fun serialize_to_json(v) do
203 v.stream.write "["
204 var is_first = true
205 for e in self do
206 if is_first then
207 is_first = false
208 else v.stream.write(", ")
209
210 if not v.try_to_serialize(e) then
211 v.warn("element of type {e.class_name} is not serializable.")
212 end
213 end
214 v.stream.write "]"
215 end
216 end