micronaut-projects / micronaut-serialization

Build Time Serialization APIs for Micronaut
Apache License 2.0
26 stars 19 forks source link

Json Type no longer works for Kotlin sealed classes in Micronaut 4.0.0 #580

Open tmalinakis-vng opened 1 year ago

tmalinakis-vng commented 1 year ago

Expected Behavior

Fields annotated with DataType.JSON should continue to be serializable and deserializable. The following should work as it used to work in Micronaut 3.x:

@MappedEntity(value = "foo")
internal data class FooEntity(
    @field:Id @GeneratedValue
    val sequence: Long?,

    @field:TypeDef(type = DataType.JSON)
    val status: FooStatus
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
sealed class FooStatus {

    @JsonTypeName("requested")
    object Requested : FooStatus()

    sealed class Processing : FooStatus() {

        @JsonTypeName("foo_status.updated")
        object Updated : Processing()

        @JsonTypeName("foo_status.no_changes")
        object NoChanges : Processing()
    }

    @JsonTypeName("errored")
    data class Errored(val reason: String?) : FooStatus()
}

Actual Behaviour

Getting a SerDe exception:

Caused by: io.micronaut.data.exceptions.DataAccessException: Failed setting JSON field parameter at index 3
    at io.micronaut.data.runtime.operations.internal.sql.AbstractSqlRepositoryOperations.getJsonValue(AbstractSqlRepositoryOperations.java:275)
Caused by: io.micronaut.data.exceptions.DataAccessException: Failed setting JSON field parameter at index 3

    at io.micronaut.data.runtime.operations.internal.sql.AbstractSqlRepositoryOperations.setStatementParameter(AbstractSqlRepositoryOperations.java:231)
    at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.access$1700(DefaultJdbcRepositoryOperations.java:136)
    at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations$JdbcParameterBinder.bindOne(DefaultJdbcRepositoryOperations.java:1064)
    at io.micronaut.data.runtime.operations.internal.query.DefaultBindableParametersStoredQuery.bindParameter(DefaultBindableParametersStoredQuery.java:178)
    at io.micronaut.data.runtime.operations.internal.query.DefaultBindableParametersStoredQuery.bindParameters(DefaultBindableParametersStoredQuery.java:85)
    at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations$JdbcEntityOperations.execute(DefaultJdbcRepositoryOperations.java:1135)
    at io.micronaut.data.runtime.operations.internal.BaseOperations.persist(BaseOperations.java:84)
    ... 72 common frames omitted
Caused by: io.micronaut.serde.exceptions.SerdeException: No serializable introspection present for type Requested. Consider adding Serdeable. Serializable annotate to type Requested. Alternatively if you are not in control of the project's source code, you can use @SerdeImport(Requested.class) to enable serialization of this type.
    at io.micronaut.serde.support.serializers.ObjectSerializer$4.tryToFindSerializer(ObjectSerializer.java:218)
Caused by: io.micronaut.serde.exceptions.SerdeException: No serializable introspection present for type Requested. Consider adding Serdeable. Serializable annotate to type Requested. Alternatively if you are not in control of the project's source code, you can use @SerdeImport(Requested.class) to enable serialization of this type.

    at io.micronaut.serde.support.serializers.ObjectSerializer$RuntimeTypeSerializer.lambda$getSerializer$0(ObjectSerializer.java:280)
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
    at io.micronaut.serde.support.serializers.ObjectSerializer$RuntimeTypeSerializer.getSerializer(ObjectSerializer.java:278)
    at io.micronaut.serde.support.serializers.ObjectSerializer$RuntimeTypeSerializer.serialize(ObjectSerializer.java:245)
    at io.micronaut.serde.jackson.JacksonJsonMapper.writeValue(JacksonJsonMapper.java:114)
    at io.micronaut.serde.jackson.JacksonJsonMapper.writeValue0(JacksonJsonMapper.java:106)
    at io.micronaut.serde.jackson.JacksonJsonMapper.writeValue0(JacksonJsonMapper.java:100)
    at io.micronaut.serde.jackson.JacksonJsonMapper.writeValueAsBytes(JacksonJsonMapper.java:201)
    at io.micronaut.data.runtime.mapper.sql.SqlJsonValueMapper.mapValue(SqlJsonValueMapper.java:52)
    at io.micronaut.data.runtime.operations.internal.sql.AbstractSqlRepositoryOperations.getJsonValue(AbstractSqlRepositoryOperations.java:273)
    ... 79 common frames omitted
Caused by: io.micronaut.core.beans.exceptions.IntrospectionException: No serializable introspection present for type Requested. Consider adding Serdeable. Serializable annotate to type Requested. Alternatively if you are not in control of the project's source code, you can use @SerdeImport(Requested.class) to enable serialization of this type.
Caused by: io.micronaut.core.beans.exceptions.IntrospectionException: No serializable introspection present for type Requested. Consider adding Serdeable. Serializable annotate to type Requested. Alternatively if you are not in control of the project's source code, you can use @SerdeImport(Requested.class) to enable serialization of this type.

    at io.micronaut.serde.support.DefaultSerdeIntrospections.getSerializableIntrospection(DefaultSerdeIntrospections.java:102)
    at io.micronaut.serde.support.serializers.SerBean.<init>(SerBean.java:107)
    at io.micronaut.serde.support.serializers.ObjectSerializer.create(ObjectSerializer.java:202)
    at io.micronaut.serde.support.serializers.ObjectSerializer.lambda$getSerBean$0(ObjectSerializer.java:193)
    at io.micronaut.core.util.SupplierUtil$2.get(SupplierUtil.java:79)
    at io.micronaut.serde.support.serializers.ObjectSerializer.getSerBean(ObjectSerializer.java:194)
    at io.micronaut.serde.support.serializers.ObjectSerializer.createSpecificInternal(ObjectSerializer.java:93)
    at io.micronaut.serde.support.serializers.ObjectSerializer.createSpecific(ObjectSerializer.java:86)
    at io.micronaut.serde.jackson.JacksonJsonMapper.writeValue(JacksonJsonMapper.java:112)
    ... 84 common frames omitted

I've followed the instructions on adding the dependencies for updating to Micronaut 4.x. I've also tried annotating the Kotlin classes with @Serdeable but then I'm hitting a "No Default constructor exists" exception which seems to be a limitation of Micronaut Serialization.

Is there a way to opt out from Micronaut Serialization back to plain Jackson?

Thanks

Steps To Reproduce

No response

Environment Information

Example Application

No response

Version

4.0.0

tmalinakis-vng commented 1 year ago

Going through the code, providing the following bean seems to restore the previous behaviour:

@Singleton
class JacksonSqlJsonValueMapper(
    private val jacksonDatabindMapper: JacksonDatabindMapper
) : SqlJsonValueMapper {

    override fun getJsonMapper(): JsonMapper {
        return jacksonDatabindMapper
    }
}

Still not sure if it's wise to rely on classes marked as Internal though.

radovanradic commented 1 year ago

There is another workaround, if that's ok with your project. Excluding micronaut serde jackson If using gradle

configurations.all {
    exclude group: "io.micronaut.serde", module: "micronaut-serde-jackson"
}

and then make sure jackson databind is imported

runtime("io.micronaut:micronaut-jackson-databind")
// or implementation

then SqlJsonValueMapper and SqlJsonColumnReader will have JacksonDatabindMapper as default JsonMapper and adding this singleton is not needed.

dstepanov commented 1 year ago

@graemerocher Do we support this case in Micronaut Serialization?

radovanradic commented 1 year ago

Documentation for configuring serde dependency in Micronaut 4: https://micronaut.io/2023/02/27/micronaut-framework-4-0-and-micronaut-jackson-databind-transitive-dependency/