lib/json: parse errors are moved to deserializer errors
[nit.git] / lib / json / serialization.nit
index 61c6f61..e92a532 100644 (file)
@@ -39,7 +39,7 @@
 # import json::serialization
 #
 # class Person
-#     auto_serializable
+#     serialize
 #
 #     var name: String
 #     var year_of_birth: Int
 # ~~~
 module serialization
 
-import ::serialization
+import ::serialization::caching
 private import ::serialization::engine_tools
 private import static
 
 # Serializer of Nit objects to Json string.
 class JsonSerializer
-       super Serializer
+       super CachingSerializer
 
        # Target writing stream
        var stream: Writer
@@ -143,9 +143,9 @@ class JsonSerializer
 
        redef fun serialize_reference(object)
        do
-               if not plain_json and refs_map.has_key(object) then
+               if not plain_json and cache.has_object(object) then
                        # if already serialized, add local reference
-                       var id = ref_id_for(object)
+                       var id = cache.id_for(object)
                        stream.write "\{\"__kind\": \"ref\", \"__id\": "
                        stream.write id.to_s
                        stream.write "\}"
@@ -154,26 +154,11 @@ class JsonSerializer
                        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
+       super CachingDeserializer
 
        # Json text to deserialize from.
        private var text: Text
@@ -184,9 +169,6 @@ class JsonDeserializer
        # Depth-first path in the serialized object tree.
        private 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`.
@@ -200,10 +182,14 @@ class JsonDeserializer
 
        redef fun deserialize_attribute(name)
        do
-               assert not path.is_empty
+               assert not path.is_empty # This is an internal error, abort
                var current = path.last
 
-               assert current.keys.has(name)
+               if not current.keys.has(name) then
+                       errors.add new Error("Deserialization Error: JSON object has not attribute '{name}'.")
+                       return null
+               end
+
                var value = current[name]
 
                return convert_object(value)
@@ -215,37 +201,69 @@ class JsonDeserializer
        do
                var id = just_opened_id
                if id == null then return # Register `new_object` only once
-               id_to_object[id] = new_object
+               cache[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 JsonParseError then
+                       errors.add object
+                       return null
+               end
+
                if object isa JsonObject then
                        assert object.keys.has("__kind")
                        var kind = object["__kind"]
 
                        # ref?
                        if kind == "ref" then
-                               assert object.keys.has("__id")
+                               if not object.keys.has("__id") then
+                                       errors.add new Error("Serialization Error: JSON object reference does not declare a `__id`.")
+                                       return object
+                               end
+
                                var id = object["__id"]
-                               assert id isa Int
+                               if not id isa Int then
+                                       errors.add new Error("Serialization Error: JSON object reference declares a non-integer `__id`.")
+                                       return object
+                               end
+
+                               if not cache.has_id(id) then
+                                       errors.add new Error("Serialization Error: JSON object reference has an unknown `__id`.")
+                                       return object
+                               end
 
-                               assert id_to_object.has_key(id)
-                               return id_to_object[id]
+                               return cache.object_for(id)
                        end
 
                        # obj?
                        if kind == "obj" then
-                               assert object.keys.has("__id")
-                               var id = object["__id"]
-                               assert id isa Int
+                               var id = null
+                               if object.keys.has("__id") then
+                                       id = object["__id"]
 
-                               assert object.keys.has("__class")
-                               var class_name = object["__class"]
-                               assert class_name isa String
+                                       if not id isa Int then
+                                               errors.add new Error("Serialization Error: JSON object declaration declares a non-integer `__id`.")
+                                               return object
+                                       end
+
+                                       if cache.has_id(id) then
+                                               errors.add new Error("Serialization Error: JSON object with `__id` {id} is deserialized twice.")
+                                               # Keep going
+                                       end
+                               end
+
+                               if not object.keys.has("__class") then
+                                       errors.add new Error("Serialization Error: JSON object declaration does not declare a `__class`.")
+                                       return object
+                               end
 
-                               assert not id_to_object.has_key(id) else print "Error: Object with id '{id}' of {class_name} is deserialized twice."
+                               var class_name = object["__class"]
+                               if not class_name isa String then
+                                       errors.add new Error("Serialization Error: JSON object declaration declares a non-string `__class`.")
+                                       return object
+                               end
 
                                # advance on path
                                path.push object
@@ -262,17 +280,23 @@ class JsonDeserializer
 
                        # char?
                        if kind == "char" then
-                               assert object.keys.has("__val")
+                               if not object.keys.has("__val") then
+                                       errors.add new Error("Serialization Error: JSON `char` object does not declare a `__val`.")
+                                       return object
+                               end
+
                                var val = object["__val"]
-                               assert val isa String
 
-                               if val.length != 1 then print "Error: expected a single char when deserializing '{val}'."
+                               if not val isa String or val.is_empty then
+                                       errors.add new Error("Serialization Error: JSON `char` object does not declare a single char in `__val`.")
+                                       return object
+                               end
 
                                return val.chars.first
                        end
 
-                       print "Malformed Json string: unexpected Json Object kind '{kind or else "null"}'"
-                       abort
+                       errors.add new Error("Serialization Error: JSON object has an unknown `__kind`.")
+                       return object
                end
 
                if object isa Array[nullable Object] then
@@ -291,7 +315,7 @@ end
 redef class Serializable
        private fun serialize_to_json(v: JsonSerializer)
        do
-               var id = v.ref_id_for(self)
+               var id = v.cache.new_id_for(self)
                v.stream.write "\{"
                if not v.plain_json then
                        v.stream.write "\"__kind\": \"obj\", \"__id\": "
@@ -376,7 +400,7 @@ redef class SimpleCollection[E]
        do
                # Register as pseudo object
                if not v.plain_json then
-                       var id = v.ref_id_for(self)
+                       var id = v.cache.new_id_for(self)
                        v.stream.write """{"__kind": "obj", "__id": """
                        v.stream.write id.to_s
                        v.stream.write """, "__class": """"
@@ -395,6 +419,7 @@ redef class SimpleCollection[E]
 
        redef init from_deserializer(v: Deserializer)
        do
+               super
                if v isa JsonDeserializer then
                        v.notify_of_creation self
                        init
@@ -425,7 +450,7 @@ redef class Map[K, V]
        redef fun serialize_to_json(v)
        do
                # Register as pseudo object
-               var id = v.ref_id_for(self)
+               var id = v.cache.new_id_for(self)
 
                if v.plain_json then
                        v.stream.write "\{"
@@ -466,10 +491,11 @@ redef class Map[K, V]
        # Instantiate a new `Array` from its serialized representation.
        redef init from_deserializer(v: Deserializer)
        do
-               init
+               super
 
                if v isa JsonDeserializer then
                        v.notify_of_creation self
+                       init
 
                        var length = v.deserialize_attribute("__length").as(Int)
                        var keys = v.path.last["__keys"].as(SequenceRead[nullable Object])