X-Git-Url: http://nitlanguage.org diff --git a/lib/json_serialization.nit b/lib/json_serialization.nit index 4b47eff..6a2d82a 100644 --- a/lib/json_serialization.nit +++ b/lib/json_serialization.nit @@ -17,6 +17,7 @@ module json_serialization import serialization +import json::static class JsonSerializer super Serializer @@ -66,6 +67,113 @@ class JsonSerializer end end +# 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] + + var just_opened_id: nullable Int = null + + init(text: Text) + do + var root = text.json_to_nit_object + if root isa HashMap[String, nullable Object] then path.add(root) + self.root = root + end + + redef fun deserialize_attribute(name) + do + assert not path.is_empty + var current = path.last + + assert current.keys.has(name) + var value = current[name] + + return convert_object(value) + end + + # This may be called multiple times by the same object from constructors + # in different nclassdef + redef fun notify_of_creation(new_object) + do + var id = just_opened_id + assert id != null + 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 + assert object.keys.has("__kind") + var kind = object["__kind"] + + # ref? + if kind == "ref" then + assert object.keys.has("__id") + var id = object["__id"] + assert id isa Int + + assert id_to_object.keys.has(id) + return id_to_object[id] + end + + # obj? + if kind == "obj" then + assert object.keys.has("__id") + var id = object["__id"] + assert id isa Int + + assert object.keys.has("__class") + 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." + + # advance on path + path.push object + + just_opened_id = id + var value = deserialize_class(class_name) + just_opened_id = null + + # revert on path + path.pop + + return value + end + + # 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 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 + + redef fun deserialize do return convert_object(root) +end + redef class Serializable private fun serialize_to_json(v: JsonSerializer) do @@ -89,17 +197,18 @@ redef class Bool 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_s}\"\}" 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("\"", "\\\"").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") + # FIXME add support for \b .replace("\b", "\\b") end redef class NativeString @@ -107,18 +216,54 @@ redef class NativeString 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 the the exact type + # We do not want Array[Int] or anything else here + 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 "]" + else + # Register as pseudo object + var id = v.ref_id_for(self) + v.stream.write "\{\"__kind\": \"obj\", \"__id\": {id}, \"__class\": \"{class_name}\"" + v.stream.write """, "__length": {{{length}}}, "__items": [""" + 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 "]" + v.stream.write "\}" + end + end + + init from_deserializer(v: Deserializer) + do + if v isa JsonDeserializer then + v.notify_of_creation self + + var length = v.deserialize_attribute("__length").as(Int) + var arr = v.path.last["__items"].as(Array[nullable Object]) + for i in length.times do + var obj = v.convert_object(arr[i]) + self.add obj end end - v.stream.write "]" end end