# This file is part of NIT ( http://www.nitlanguage.org ). # # Copyright 2014 Alexis Laferrière # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 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 json::static # Serializer of Nit objects to Json string. class JsonSerializer super Serializer # Target writing stream var stream: Writer redef fun serialize(object) do if object == null then stream.write "null" else object.serialize_to_json(self) end redef fun serialize_attribute(name, value) do stream.write ", \"{name}\": " super end redef fun serialize_reference(object) do 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}\}" else # serialize here serialize object end end # 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.has_key(object) then return refs_map[object] else var id = refs_map.length refs_map[object] = id return id end end end # Deserializer from a Json string. class JsonDeserializer super Deserializer # 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 do var root = text.parse_json if root isa JsonObject 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 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 JsonObject 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.has_key(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.has_key(id) else print "Error: Object with id '{id}' of {class_name} 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 var id = v.ref_id_for(self) v.stream.write "\{\"__kind\": \"obj\", \"__id\": {id}, \"__class\": \"{class_name}\"" core_serialize_to(v) v.stream.write "\}" end end redef class Int redef fun serialize_to_json(v) do v.stream.write(to_s) end redef class Float redef fun serialize_to_json(v) do v.stream.write(to_s) end redef class Bool redef fun serialize_to_json(v) do v.stream.write(to_s) end redef class Char 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) 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 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 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