* Use `GetName` to better restrict the accepted static type for attributes with formal types. This should complete the protection against the injection of unexpected types through deserialization (reported by @ppepos).
* Update `nitserial` to intrude import modules with private serializable classes, instead of skipping them. Serializing private classes from the lib should probably be avoided, but at least now we support them and we can see them.
* Fix the deserialization of maps from JSON with metadata and cycles. A difference in the deserialization order could cause a reference to be read before the referenced object. This issue may still be caused by versionning, so a foolproof solution should probably be added to the JSON deserializer.
* Fix a warning in generated code, it was generated for each `serialize` class (quite a lot). It caused 25 warnings just in lib/github/api.nit.
* Implement serialization for the private classes of `core::queue` and don't crash on deserialization errors in `a_star`.
Pull-Request: #2531
Reviewed-by: Jean Privat <jean@pryen.org>
do
deserializer.notify_of_creation self
- var graph = deserializer.deserialize_attribute("graph")
- assert graph isa Graph[N, Link]
+ var graph = deserializer.deserialize_attribute("graph", (new GetName[Graph[N, Link]]).to_s)
+ if not graph isa Graph[N, Link] then graph = new Graph[N, Link]
self.graph = graph
end
end
super Serializable
# Nodes in this graph
- var nodes: Set[N] = new HashSet[N]
+ var nodes = new Set[N]
# Links in this graph
- var links: Set[L] = new HashSet[L]
+ var links = new Set[L]
# Add a `node` to this graph
fun add_node(node: N): N
do
deserializer.notify_of_creation self
- var nodes = deserializer.deserialize_attribute("nodes")
- assert nodes isa HashSet[N]
- self.nodes = nodes
+ var nodes = deserializer.deserialize_attribute("nodes", (new GetName[Set[N]]).to_s)
+ if deserializer.deserialize_attribute_missing then
+ deserializer.errors.add new AttributeMissingError(self, "nodes")
+ end
+ if nodes isa Set[N] then self.nodes = nodes
- var links = deserializer.deserialize_attribute("links")
- assert links isa HashSet[L]
- for link in links do add_link link
+ var links = deserializer.deserialize_attribute("links", (new GetName[Set[L]]).to_s)
+ if deserializer.deserialize_attribute_missing then
+ deserializer.errors.add new AttributeMissingError(self, "links")
+ end
+ if links isa Set[L] then for link in links do add_link link
end
end
# Annotations on the module declaration
var annotations = new Array[Writable]
- # Imports from this module
- var imports = new Array[Writable]
+ # Importation declarations
+ #
+ # Accepts two formats:
+ # * Module name only, short or qualified: `json`, `gamnit::flat`, etc.
+ # * Full importation declaration: `import json`, `private import gamnit::flat`, etc.
+ var imports = new Set[Writable]
# Main content of this module
var content = new Array[Writable]
add "end\n\n"
end
- for i in imports do add "import {i}\n"
+ for i in imports do
+ if i.to_s.has("import ") then
+ add i
+ else
+ add "import "
+ add i
+ end
+ add "\n"
+ end
add "\n"
for l in content do
# ref?
if kind == "ref" then
if not object.keys.has("__id") then
- errors.add new Error("Serialization Error: JSON object reference does not declare a `__id`.")
+ errors.add new Error("Deserialization Error: JSON object reference does not declare a `__id`.")
return object
end
var id = object["__id"]
if not id isa Int then
- errors.add new Error("Serialization Error: JSON object reference declares a non-integer `__id`.")
+ errors.add new Error("Deserialization 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`.")
+ errors.add new Error("Deserialization Error: JSON object reference has an unknown `__id`.")
return object
end
id = object["__id"]
if not id isa Int then
- errors.add new Error("Serialization Error: JSON object declaration declares a non-integer `__id`.")
+ errors.add new Error("Deserialization 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.")
+ errors.add new Error("Deserialization Error: JSON object with `__id` {id} is deserialized twice.")
# Keep going
end
end
end
if class_name == null then
- errors.add new Error("Serialization Error: JSON object declaration does not declare a `__class`.")
+ errors.add new Error("Deserialization Error: JSON object declaration does not declare a `__class`.")
return object
end
if not class_name isa String then
- errors.add new Error("Serialization Error: JSON object declaration declares a non-string `__class`.")
+ errors.add new Error("Deserialization Error: JSON object declaration declares a non-string `__class`.")
return object
end
# char?
if kind == "char" then
if not object.keys.has("__val") then
- errors.add new Error("Serialization Error: JSON `char` object does not declare a `__val`.")
+ errors.add new Error("Deserialization Error: JSON `char` object does not declare a `__val`.")
return object
end
var val = object["__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`.")
+ errors.add new Error("Deserialization Error: JSON `char` object does not declare a single char in `__val`.")
return object
end
return val.chars.first
end
- errors.add new Error("Serialization Error: JSON object has an unknown `__kind`.")
+ errors.add new Error("Deserialization Error: JSON object has an unknown `__kind`.")
return object
end
open_array = arr
end
- # Try to get the name of the single parameter type assuming it is E.
- # This does not work in non-generic subclasses,
- # when the first parameter is not E, or
- # when there is more than one parameter. (The last one could be fixed)
- var class_name = class_name
- var items_type = null
- var bracket_index = class_name.index_of('[')
- if bracket_index != -1 then
- var start = bracket_index + 1
- var ending = class_name.last_index_of(']')
- items_type = class_name.substring(start, ending-start)
- end
+ # Name of the dynamic name of E
+ var items_type_name = (new GetName[E]).to_s
# Fill array
for o in open_array do
- var obj = v.convert_object(o, items_type)
+ var obj = v.convert_object(o, items_type_name)
if obj isa E then
add obj
- else v.errors.add new AttributeTypeError(self, "items", obj, "E")
+ else v.errors.add new AttributeTypeError(self, "items", obj, items_type_name)
end
end
end
v.notify_of_creation self
init
+ var keys_type_name = (new GetName[K]).to_s
+ var values_type_name = (new GetName[V]).to_s
+
var length = v.deserialize_attribute("__length")
var keys = v.path.last.get_or_null("__keys")
var values = v.path.last.get_or_null("__values")
if keys == null and values == null then
# Fallback to a plain object
for key, value_src in v.path.last do
- var value = v.convert_object(value_src)
+
+ var value = v.convert_object(value_src, values_type_name)
if not key isa K then
- v.errors.add new AttributeTypeError(self, "keys", key, "K")
+ v.errors.add new AttributeTypeError(self, "keys", key, keys_type_name)
continue
end
if not value isa V then
- v.errors.add new AttributeTypeError(self, "values", value, "V")
+ v.errors.add new AttributeTypeError(self, "values", value, values_type_name)
continue
end
return
end
+ # First, convert all keys to follow the order of the serialization
+ var converted_keys = new Array[K]
for i in length.times do
- var key = v.convert_object(keys[i])
- var value = v.convert_object(values[i])
+ var key = v.convert_object(keys[i], keys_type_name)
if not key isa K then
- v.errors.add new AttributeTypeError(self, "keys", key, "K")
+ v.errors.add new AttributeTypeError(self, "keys", key, keys_type_name)
continue
end
+ converted_keys.add key
+ end
+
+ # Then convert the values and build the map
+ for i in length.times do
+ var key = converted_keys[i]
+ var value = v.convert_object(values[i], values_type_name)
+
if not value isa V then
- v.errors.add new AttributeTypeError(self, "values", value, "V")
+ v.errors.add new AttributeTypeError(self, "values", value, values_type_name)
continue
end
new_annotation serialize_as
end
+intrude import core::queue
+import meta
+
# Abstract serialization service to be sub-classed by specialized services.
interface Serializer
# Entry point method of this service, serialize the `object`
# Deserialize the attribute with `name` from the object open for deserialization
#
- # The `static_type` can be used as last resort if the deserialized object
- # desn't have any metadata declaring the dynamic type.
+ # The `static_type` restricts what kind of object can be deserialized.
#
# Return the deserialized value or null on error, and set
# `deserialize_attribute_missing` to whether the attribute was missing.
var errors = new Array[Error]
end
-# Error on invalid dynamic type for a deserialized attribute
-class AttributeTypeError
+# Deserialization error related to an attribute of `receiver`
+abstract class AttributeError
super Error
- autoinit receiver, attribute_name, attribute, expected_type
-
# Parent object of the problematic attribute
var receiver: Object
# Name of the problematic attribute in `receiver`
var attribute_name: String
+end
+
+# Invalid dynamic type for a deserialized attribute
+class AttributeTypeError
+ super AttributeError
+
+ autoinit receiver, attribute_name, attribute, expected_type
# Deserialized object that isn't of the `expected_type`
var attribute: nullable Object
end
end
+# Missing attribute at deserialization
+class AttributeMissingError
+ super AttributeError
+
+ autoinit receiver, attribute_name
+
+ redef var message is lazy do
+ return "Deserialization Error: Missing attribute `{receiver.class_name}::{attribute_name}`"
+ end
+end
+
# Instances of this class can be passed to `Serializer::serialize`
interface Serializable
# Serialize `self` to `serializer`
v.serialize_attribute("cause", cause)
end
end
+
+# ---
+# core::queue classes
+
+redef abstract class ProxyQueue[E]
+
+ redef init from_deserializer(v)
+ do
+ v.notify_of_creation self
+
+ var seq = v.deserialize_attribute("seq", (new GetName[Sequence[E]]).to_s)
+ if not seq isa Sequence[E] then seq = new Array[E]
+ if v.deserialize_attribute_missing then
+ v.errors.add new AttributeMissingError(self, "seq")
+ end
+
+ init seq
+ end
+
+ redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
+end
+
+redef class RandQueue[E]
+
+ redef init from_deserializer(v)
+ do
+ v.notify_of_creation self
+
+ var seq = v.deserialize_attribute("seq", (new GetName[SimpleCollection[E]]).to_s)
+ if not seq isa SimpleCollection[E] then seq = new Array[E]
+ if v.deserialize_attribute_missing then
+ v.errors.add new AttributeMissingError(self, "seq")
+ end
+
+ init seq
+ end
+
+ redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
+end
+
+redef class MinHeap[E]
+
+ redef init from_deserializer(v)
+ do
+ v.notify_of_creation self
+
+ var items = v.deserialize_attribute("items", (new GetName[SimpleCollection[E]]).to_s)
+ if not items isa Array[E] then items = new Array[E]
+ if v.deserialize_attribute_missing then
+ v.errors.add new AttributeMissingError(self, "items")
+ end
+
+ var comparator = v.deserialize_attribute("comparator", "Comparator")
+ if not comparator isa Comparator then comparator = default_comparator
+ if v.deserialize_attribute_missing then
+ v.errors.add new AttributeMissingError(self, "comparator")
+ end
+
+ init comparator
+ self.items.add_all items
+ end
+
+ redef fun core_serialize_to(v)
+ do
+ v.serialize_attribute("items", items)
+ v.serialize_attribute("comparator", comparator)
+ end
+end
do
var code = new Array[String]
code.add """
-redef init from_deserializer(v: Deserializer)
+redef init from_deserializer(v)
do
super
v.notify_of_creation self
var type_name = mtype.to_s
var name = attribute.name
- var resolved_type_name = type_name
- var mclassdef = nclassdef.mclassdef
- if mclassdef != null then
- var bound_mtype = mclassdef.bound_mtype
- var resolved_mtype = mtype.resolve_for(bound_mtype, bound_mtype, mclassdef.mmodule, true)
- resolved_type_name = resolved_mtype.name
-
- # TODO Use something like `V.class_name` to get the precise runtime type of virtual types.
- # We currently use the upper bound of virtual types as static type in generated code
- # for type suggestion and to prevent loading unexected types.
- # This leaves a security issue when, for example, `DefaultMap::default_value`
- # is bound to `nullable Object` and would allow any object to be loaded.
+ if mtype.need_anchor then
+ # Check dynamic type of virtual params
+ code.add """
+ var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", (new GetName[{{{mtype}}}]).to_s)
+"""
+ else
+ # No param to check
+ code.add """
+ var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{type_name}}}")
+"""
end
if type_name == "nullable Object" then
# Don't type check
code.add """
- self.{{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
+ self.{{{name}}} = {{{name}}}
"""
else
+ # Needs type check
code.add """
- var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{resolved_type_name}}}")
if v.deserialize_attribute_missing then
"""
# What to do when an attribute is missing?
if attribute.has_value then
# Leave it to the default value
else if mtype isa MNullableType then
+ # This is always a nullable type
code.add """
self.{{{name}}} = null"""
+ else if mtype isa MFormalType then
+ # This type nullability may change in subclasses
+ code.add """
+ var n___{{{name}}} = null
+ if n___{{{name}}} isa {{{mtype}}} then
+ self.{{{name}}} = n___{{{name}}}
+ else
+ v.errors.add new AttributeMissingError(self, "{{{name}}}")
+ end"""
else code.add """
- v.errors.add new Error("Deserialization Error: attribute `{class_name}::{{{name}}}` missing from JSON object")"""
+ v.errors.add new AttributeMissingError(self, "{{{name}}}")"""
code.add """
else if not {{{name}}} isa {{{type_name}}} then
- v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{resolved_type_name}}}")
+ v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name}}}")
if v.keep_going == false then return
else
self.{{{name}}} = {{{name}}}
end
redef class MType
- # Is this type fully visible from `mmodule`?
- fun is_visible_from(mmodule: MModule): Bool is abstract
+ # List classes composing this type
+ private fun related_mclasses(mmodule: MModule): Array[MClass] is abstract
end
redef class MClassType
- redef fun is_visible_from(mmodule) do
- return mmodule.is_visible(mclass.intro_mmodule, mclass.visibility)
- end
+ redef fun related_mclasses(mmodule) do return [mclass]
end
-redef class MNullableType
- redef fun is_visible_from(mmodule) do return mtype.is_visible_from(mmodule)
+redef class MProxyType
+ redef fun related_mclasses(mmodule) do return undecorate.related_mclasses(mmodule)
end
redef class MGenericType
- redef fun is_visible_from(mmodule)
+ redef fun related_mclasses(mmodule)
do
- for arg_mtype in arguments do if not arg_mtype.is_visible_from(mmodule) then return false
- return super
+ var mods = super
+ for arg_mtype in arguments do mods.add_all(arg_mtype.related_mclasses(mmodule))
+ return mods
end
end
# and which are visible.
if mtype isa MGenericType and
mtype.is_subtype(m, null, serializable_type) and
- mtype.is_visible_from(mmodule) and
mtype.mclass.kind == concrete_kind and
not compiled_types.has(mtype) then
+ # Intrude import the modules declaring private classes
+ var related_mclasses = mtype.related_mclasses(mmodule)
+ for mclass in related_mclasses do
+ if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then
+ var intro_mmodule = mclass.intro_mmodule
+ var intro_mgroup = intro_mmodule.mgroup
+
+ var to_import = intro_mmodule.full_name
+ if intro_mgroup == null or intro_mgroup.default_mmodule == intro_mmodule then
+ to_import = intro_mmodule.name
+ end
+
+ nit_module.imports.add "intrude import {to_import}"
+ end
+ end
+
compiled_types.add mtype
nit_module.content.add """
if name == \"{{{mtype}}}\" then return new {{{mtype}}}.from_deserializer(self)"""
--- /dev/null
+Deserialization Error: `DangerSub` is not a subtype of the static type `nullable A`
+Deserialization Error: `DangerSub` is not a subtype of the static type ``
+<A other:null next:null>
+---
+DANGER 'My text 1'
+DANGER 'My text 2'
+Deserialization Error: Wrong type on `A::other` expected `nullable A`, got `DangerSub`
+Deserialization Error: Wrong type on `A::next` expected `C`, got `DangerSub`
+<A other:null next:null>
+---
+Deserialization Error: `B` is not a subtype of the static type ``
+<B other:null next:null>
+---
+Deserialization Error: `A` is not a subtype of the static type ``
+<B other:null next:null>
+---
+Deserialization Error: `A` is not a subtype of the static type ``
+Deserialization Error: Wrong type on `G::e` expected `E`, got `null`
+<G e:not-set>
+---
+Deserialization Error: `A` is not a subtype of the static type ``
+Deserialization Error: Wrong type on `G::e` expected `E`, got `null`
+<G e:not-set>
Runtime error: Uninitialized attribute _s (alt/test_json_deserialization_plain_alt2.nit:27)
# JSON: {"__class": "MyClass", "i": 123, "o": null}
-# Errors: 'Deserialization Error: attribute `MyClass::s` missing from JSON object', 'Deserialization Error: attribute `MyClass::f` missing from JSON object', 'Deserialization Error: attribute `MyClass::a` missing from JSON object'
+# Errors: 'Deserialization Error: Missing attribute `MyClass::s`', 'Deserialization Error: Missing attribute `MyClass::f`', 'Deserialization Error: Missing attribute `MyClass::a`'
import test_serialization
import serialization
+intrude import serialization::engine_tools
redef class Deserializer
redef fun deserialize_class(name)
if name == "Array[Text]" then return new Array[Text].from_deserializer(self)
if name == "Array[Map[String, nullable Object]]" then return new Array[Map[String, nullable Object]].from_deserializer(self)
if name == "Array[String]" then return new Array[String].from_deserializer(self)
+ if name == "StrictHashMap[Int, Object]" then return new StrictHashMap[Int, Object].from_deserializer(self)
if name == "Array[Error]" then return new Array[Error].from_deserializer(self)
if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
if name == "Array[Int]" then return new Array[Int].from_deserializer(self)
if name == "Array[Float]" then return new Array[Float].from_deserializer(self)
if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
+ if name == "StrictHashMap[Serializable, Int]" then return new StrictHashMap[Serializable, Int].from_deserializer(self)
if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
if name == "Array[Match]" then return new Array[Match].from_deserializer(self)
# Nit: <MyClass i:123 s:hello f:123.456 a:[one, two] o:<MyClass i:456 s:world f:654.321 a:[1, 2] o:<null>>>
# JSON: {"i": 123, "s": "hello", "f": 123.456, "a": ["one", "two"], "o": null}
-# Errors: 'Serialization Error: JSON object declaration does not declare a `__class`.'
+# Errors: 'Deserialization Error: JSON object declaration does not declare a `__class`.'
# Nit: <JsonObject i:123, s:hello, f:123.456, a:[one,two], o:<null>>
# JSON: {"__class": "MyClass", "i": 123, "s": "hello", "f": 123.456, "a": ["one", "two"], "o": "Not the right type"}
Runtime error: Uninitialized attribute _s (alt/test_json_deserialization_plain_alt2.nit:22)
# JSON: {"__class": "MyClass", "i": 123, "o": null}
-# Errors: 'Deserialization Error: attribute `MyClass::s` missing from JSON object', 'Deserialization Error: attribute `MyClass::f` missing from JSON object', 'Deserialization Error: attribute `MyClass::a` missing from JSON object'
+# Errors: 'Deserialization Error: Missing attribute `MyClass::s`', 'Deserialization Error: Missing attribute `MyClass::f`', 'Deserialization Error: Missing attribute `MyClass::a`'
-Deserialization Error: `DangerSub` is not a subtype of the static type `MyClass`
-Deserialization Error: Wrong type on `MyClass::other` expected `MyClass`, got `null`
-Deserialization Error: `DangerSub` is not a subtype of the static type `MyClass`
-Deserialization Error: Wrong type on `MyClass::next` expected `MyClass`, got `null`
+Deserialization Error: `DangerSub` is not a subtype of the static type `nullable A`
+Deserialization Error: `DangerSub` is not a subtype of the static type `nullable A`
+<A other:null next:null>
+---
+DANGER 'My text 1'
+DANGER 'My text 2'
+Deserialization Error: Wrong type on `A::other` expected `nullable A`, got `DangerSub`
+Deserialization Error: Wrong type on `A::next` expected `C`, got `DangerSub`
+<A other:null next:null>
+---
+
+<B other:null next:<B other:null next:null>>
+---
+Deserialization Error: `A` is not a subtype of the static type `nullable B`
+<B other:null next:null>
+---
+
+<G[A] e:<A other:null next:null>>
+---
+Deserialization Error: `A` is not a subtype of the static type `B`
+Deserialization Error: Wrong type on `G[B]::e` expected `E`, got `null`
+<G[B] e:not-set>
+++ /dev/null
-DANGER 'My text 1'
-DANGER 'My text 2'
-Deserialization Error: Wrong type on `MyClass::other` expected `MyClass`, got `DangerSub`
-Deserialization Error: Wrong type on `MyClass::next` expected `MyClass`, got `DangerSub`
var with_generics: Array[Serializable] = [a, b, c, d, e, fi, ff, g: Serializable]
end
-# We instanciate it here so that `nitserial` detects generic types as being alive
+# We instantiate it here so that `nitserial` detects generic types as being alive
var entities = new TestEntities
import json
-class MyClass
+class A
serialize
- var other: MyClass
+ var other: nullable A
- type C: MyClass
+ type C: nullable A
var next: C
+
+ redef fun to_s do return "<{class_name} other:{other or else "null"} next:{next or else "null"}>"
+end
+
+class B
+ super A
+ serialize
+
+ redef type C: nullable B
end
class DangerSup
end
end
-var json_with_metadata = """{
- "__class": "MyClass",
+class G[E: A]
+ serialize
+ var e: E
+
+ redef fun to_s do return "<{class_name} e:{if isset _e then e.to_s else "not-set"}>"
+end
+
+redef class Deserializer
+ redef fun deserialize_class(name)
+ do
+ if name == "G[A]" then return new G[A].from_deserializer(self)
+ if name == "G[B]" then return new G[B].from_deserializer(self)
+ return super
+ end
+end
+
+var json = """{
+ "__class": "A",
"other": {"__class": "DangerSub", "dangerous_setter": "My text 1"},
"next": {"__class": "DangerSub", "dangerous_setter": "My text 2"}
}"""
-var deserializer = new JsonDeserializer(json_with_metadata)
-#alt1#deserializer.check_subtypes = false
-deserializer.deserialize
+# OK, accept only valid types, so don't try to deserialize DangerSub
+var deserializer = new JsonDeserializer(json)
+var res = deserializer.deserialize
+print deserializer.errors.join("\n")
+print res or else "null"
+
+print "---"
+
+# Accept any types, so deserialize the dangerous classes
+deserializer = new JsonDeserializer(json)
+deserializer.check_subtypes = false
+res = deserializer.deserialize
+print deserializer.errors.join("\n")
+print res or else "null"
+
+print "---"
+
+# Valid virtual type for `next: B`
+json = """{"__class": "B", "next": {"__class": "B"}}"""
+deserializer = new JsonDeserializer(json)
+res = deserializer.deserialize
+print deserializer.errors.join("\n")
+print res or else "null"
+
+print "---"
+
+# Virtual type error for `next: B`
+json = """{"__class": "B", "next": {"__class": "A"}}"""
+deserializer = new JsonDeserializer(json)
+res = deserializer.deserialize
+print deserializer.errors.join("\n")
+print res or else "null"
+
+print "---"
+
+# Valid parameter type for `e: A`
+json = """{"__class": "G[A]", "e": {"__class": "A"}}"""
+deserializer = new JsonDeserializer(json)
+res = deserializer.deserialize
+print deserializer.errors.join("\n")
+print res or else "null"
+
+print "---"
+
+# Parameter type error for `e: B`
+json = """{"__class": "G[B]", "e": {"__class": "A"}}"""
+deserializer = new JsonDeserializer(json)
+res = deserializer.deserialize
print deserializer.errors.join("\n")
+print res or else "null"