Repository navigation
DataType nested class #626
Description
Description
The Java contract API appears to use inconsistent names for nested @DataType classes when creating schema references and registering types.
TypeRegistryImpl.addDataType(Class<?>) registers a data type using DataTypeDefinitionImpl.getSimpleName(). For a nested class such as Outer.Inner, Class.getSimpleName() returns Inner, so the registry key is Inner.
However, TypeSchema.typeConvert(Class<?>) uses Class.getTypeName() and passes that name to updateSchemaForClass(...). For Outer.Inner, the class name contains Outer$Inner. In TypeSchema.updateSchemaForClass(...), this line:
schema.put("$ref", "#/components/schemas/" + className.substring(className.lastIndexOf('.') + 1));produces:
#/components/schemas/Outer$Inner
instead of:
#/components/schemas/Inner
This creates a registry lookup mismatch. During deserialization, the schema reference resolves to Outer$Inner, but the type registry contains the data type under Inner. The lookup returns null, and JSONTransactionSerializer.createComponentInstance(...) then throws a NullPointerException when it tries to use the missing definition.
The reference mismatch can also be observed directly by calling TypeSchema.typeConvert(Outer.Inner.class) from a smart contract: its "$ref" is #/components/schemas/Outer$Inner, while the registry uses the key Inner.
Steps to reproduce
-
Define a nested
@DataTypeclass:public class Outer { @DataType public static class Inner { @Property private String value; public Inner() { } } }
-
Define a contract transaction that accepts the nested data type:
@Contract(name = "NestedTypeContract") public class NestedTypeContract implements ContractInterface { @Transaction public Outer.Inner echo(final Context context, final Outer.Inner input) { return input; } @Transaction public String inspectSchema(final Context context) { return TypeSchema.typeConvert(Outer.Inner.class).getRef(); } }
-
Start the contract so the shim scans the
@DataTypeclass. The type registry registersOuter.Innerunder its simple name:Inner -
Invoke
inspectSchema. Observe thatTypeSchema.typeConvert(Outer.Inner.class).getRef()returns:#/components/schemas/Outer$InnerThis differs from the registry key,
Inner. -
Submit the
echotransaction with a JSON argument representingOuter.Inner:{"value":"test"} -
Observe that deserialization attempts to look up
Outer$Innerin the type registry, getsnull, and fails with aNullPointerExceptioninJSONTransactionSerializer.createComponentInstance().
Activity