# See the License for the specific language governing permissions and
# limitations under the License.
+# Handles serialization and deserialization of objects to/from Json.
module json_serialization
import serialization
-import simple_json_reader
+import json::static
+# Serializer of Nit objects to Json string.
class JsonSerializer
super Serializer
# Target writing stream
- var stream: OStream
-
- init(stream: OStream) do self.stream = stream
+ var stream: Writer
redef fun serialize(object)
do
redef fun serialize_reference(object)
do
- if refs_map.keys.has(object) then
+ if refs_map.has_key(object) then
# if already serialized, add local reference
var id = ref_id_for(object)
stream.write "\{\"__kind\": \"ref\", \"__id\": {id}\}"
end
end
- # Map of references to already serialized objects
- var refs_map = new HashMap[Serializable,Int]
+ # Map of references to already serialized objects.
+ private var refs_map = new StrictHashMap[Serializable,Int]
+ # Get the internal serialized reference for this `object`.
private fun ref_id_for(object: Serializable): Int
do
- if refs_map.keys.has(object) then
+ if refs_map.has_key(object) then
return refs_map[object]
else
var id = refs_map.length
end
end
-# Deserializer from a Json string
+# Deserializer from a Json string.
class JsonDeserializer
super Deserializer
- var root: nullable Object
- var path = new Array[HashMap[String, nullable Object]]
- var id_to_object = new HashMap[Int, Object]
+ # Json text to deserialize from.
+ private var text: Text
+
+ # Root json object parsed from input text.
+ var root: nullable Jsonable is noinit
+
+ # Depth-first path in the serialized object tree.
+ var path = new Array[JsonObject]
+ # Map of references to already deserialized objects.
+ private var id_to_object = new StrictHashMap[Int, Object]
+
+ # Last encountered object reference id.
+ #
+ # See `id_to_object`.
var just_opened_id: nullable Int = null
- init(text: String)
- do
- var root = text.json_to_nit_object
- if root isa HashMap[String, nullable Object] then path.add(root)
+ init do
+ var root = text.parse_json
+ if root isa JsonObject then path.add(root)
self.root = root
end
assert current.keys.has(name)
var value = current[name]
-
+
return convert_object(value)
end
redef fun notify_of_creation(new_object)
do
var id = just_opened_id
- assert id != null
+ if id == null then return # Register `new_object` only once
id_to_object[id] = new_object
end
# Convert from simple Json object to Nit object
private fun convert_object(object: nullable Object): nullable Object
do
- if object isa HashMap[String, nullable Object] then
+ if object isa JsonObject then
assert object.keys.has("__kind")
var kind = object["__kind"]
var id = object["__id"]
assert id isa Int
- assert id_to_object.keys.has(id)
+ assert id_to_object.has_key(id)
return id_to_object[id]
end
var class_name = object["__class"]
assert class_name isa String
- assert not id_to_object.keys.has(id) else print "Error: Object with id '{id}' is deserialized twice."
+ assert not id_to_object.has_key(id) else print "Error: Object with id '{id}' of {class_name} is deserialized twice."
# advance on path
path.push object
return value
end
- # char? TODO
+ # char?
+ if kind == "char" then
+ assert object.keys.has("__val")
+ var val = object["__val"]
+ assert val isa String
+
+ if val.length != 1 then print "Error: expected a single char when deserializing '{val}'."
+
+ return val.chars.first
+ end
- print "Malformed Json string: unexpected Json Object kind '{kind}'"
+ print "Malformed Json string: unexpected Json Object kind '{kind or else "null"}'"
abort
end
+ if object isa Array[nullable Object] then
+ # special case, isa Array[nullable Serializable]
+ var array = new Array[nullable Serializable]
+ for e in object do array.add e.as(nullable Serializable)
+ return array
+ end
+
return object
end
end
redef class Char
- redef fun serialize_to_json(v) do v.stream.write("'{to_s}'")
+ redef fun serialize_to_json(v) do v.stream.write "\{\"__kind\": \"char\", \"__val\": {to_s.to_json}\}"
end
redef class String
- redef fun serialize_to_json(v) do v.stream.write("\"{to_json_s}\"")
-
- private fun to_json_s: String do return self.replace("\\", "\\\\").
- replace("\"", "\\\"").replace("\b", "\\b").replace("/", "\\/").
- replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
- # FIXME add support for unicode char when supported by Nit strings
- # FIXME add support for \f! # .replace("\f", "\\f")
+ redef fun serialize_to_json(v) do v.stream.write(to_json)
end
redef class NativeString
redef fun serialize_to_json(v) do to_s.serialize_to_json(v)
end
+redef class Collection[E]
+ # Utility to serialize a normal Json array
+ private fun serialize_to_pure_json(v: JsonSerializer)
+ do
+ v.stream.write "["
+ var is_first = true
+ for e in self do
+ if is_first then
+ is_first = false
+ else v.stream.write ", "
+
+ if not v.try_to_serialize(e) then
+ v.warn("element of type {e.class_name} is not serializable.")
+ end
+ end
+ v.stream.write "]"
+ end
+end
+
+redef class SimpleCollection[E]
+ redef fun serialize_to_json(v)
+ do
+ # Register as pseudo object
+ var id = v.ref_id_for(self)
+ v.stream.write """{"__kind": "obj", "__id": """
+ v.stream.write id.to_s
+ v.stream.write """, "__class": """"
+ v.stream.write class_name
+ v.stream.write """", "__length": """
+ v.stream.write length.to_s
+ v.stream.write """, "__items": """
+ serialize_to_pure_json v
+ v.stream.write "\}"
+ end
+
+ redef init from_deserializer(v: Deserializer)
+ do
+ if v isa JsonDeserializer then
+ v.notify_of_creation self
+ init
+
+ var length = v.deserialize_attribute("__length").as(Int)
+ var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
+ for i in length.times do
+ var obj = v.convert_object(arr[i])
+ self.add obj
+ end
+ end
+ end
+end
+
redef class Array[E]
- redef fun serialize_to_json(v) do
- v.stream.write "["
- var is_first = true
- for e in self do
- if is_first then
- is_first = false
- else v.stream.write(", ")
-
- if not v.try_to_serialize(e) then
- v.warn("element of type {e.class_name} is not serializable.")
+ redef fun serialize_to_json(v)
+ do
+ if class_name == "Array[nullable Serializable]" then
+ # Using class_name to get the exact type,
+ # we do not want Array[Int] or anything else here.
+
+ serialize_to_pure_json v
+ else super
+ end
+end
+
+redef class Map[K, V]
+ redef fun serialize_to_json(v)
+ do
+ # Register as pseudo object
+ var id = v.ref_id_for(self)
+
+ v.stream.write """{"__kind": "obj", "__id": """
+ v.stream.write id.to_s
+ v.stream.write """, "__class": """"
+ v.stream.write class_name
+ v.stream.write """", "__length": """
+ v.stream.write length.to_s
+ v.stream.write """, "__keys": """
+
+ keys.serialize_to_pure_json v
+
+ v.stream.write """, "__values": """
+ values.serialize_to_pure_json v
+ v.stream.write "\}"
+ end
+
+ # Instantiate a new `Array` from its serialized representation.
+ redef init from_deserializer(v: Deserializer)
+ do
+ init
+
+ if v isa JsonDeserializer then
+ v.notify_of_creation self
+
+ var length = v.deserialize_attribute("__length").as(Int)
+ var keys = v.path.last["__keys"].as(SequenceRead[nullable Object])
+ var values = v.path.last["__values"].as(SequenceRead[nullable Object])
+ for i in length.times do
+ var key = v.convert_object(keys[i])
+ var value = v.convert_object(values[i])
+ self[key] = value
+ end
+ end
+ end
+end
+
+# Maps instances to a value, uses `is_same_instance`
+#
+# Warning: This class does not implement all the services from `Map`.
+private class StrictHashMap[K, V]
+ super Map[K, V]
+
+ # private
+ var map = new HashMap[K, Array[Couple[K, V]]]
+
+ redef var length = 0
+
+ redef fun is_empty do return length == 0
+
+ # private
+ fun node_at(key: K): nullable Couple[K, V]
+ do
+ if not map.keys.has(key) then return null
+
+ var arr = map[key]
+ for couple in arr do
+ if couple.first.is_same_serialized(key) then
+ return couple
end
end
- v.stream.write "]"
+
+ return null
+ end
+
+ redef fun [](key)
+ do
+ var node = node_at(key)
+ assert node != null
+ return node.second
+ end
+
+ redef fun []=(key, value)
+ do
+ var node = node_at(key)
+ if node != null then
+ node.second = value
+ return
+ end
+
+ var arr
+ if not map.keys.has(key) then
+ arr = new Array[Couple[K, V]]
+ map[key] = arr
+ else arr = map[key]
+
+ arr.add new Couple[K, V](key, value)
+ self.length += 1
end
+
+ redef fun has_key(key) do return node_at(key) != null
end