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 @DataType class:
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 @DataType class. The type registry registers Outer.Inner under its simple name:
-
Invoke inspectSchema. Observe that TypeSchema.typeConvert(Outer.Inner.class).getRef() returns:
#/components/schemas/Outer$Inner
This differs from the registry key, Inner.
-
Submit the echo transaction with a JSON argument representing Outer.Inner:
-
Observe that deserialization attempts to look up Outer$Inner in the type registry, gets null, and fails with a NullPointerException in JSONTransactionSerializer.createComponentInstance().
Description
The Java contract API appears to use inconsistent names for nested
@DataTypeclasses when creating schema references and registering types.TypeRegistryImpl.addDataType(Class<?>)registers a data type usingDataTypeDefinitionImpl.getSimpleName(). For a nested class such asOuter.Inner,Class.getSimpleName()returnsInner, so the registry key isInner.However,
TypeSchema.typeConvert(Class<?>)usesClass.getTypeName()and passes that name toupdateSchemaForClass(...). ForOuter.Inner, the class name containsOuter$Inner. InTypeSchema.updateSchemaForClass(...), this line:produces:
instead of:
This creates a registry lookup mismatch. During deserialization, the schema reference resolves to
Outer$Inner, but the type registry contains the data type underInner. The lookup returnsnull, andJSONTransactionSerializer.createComponentInstance(...)then throws aNullPointerExceptionwhen 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 keyInner.Steps to reproduce
Define a nested
@DataTypeclass:Define a contract transaction that accepts the nested data type:
Start the contract so the shim scans the
@DataTypeclass. The type registry registersOuter.Innerunder its simple name:Invoke
inspectSchema. Observe thatTypeSchema.typeConvert(Outer.Inner.class).getRef()returns:This 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().