Kotlin & Jackson: type error when specifying custom serialisation for a data class field - spring-boot

I have a Kotlin data class that is serialised to JSON in a Spring Boot project. I'd like to customise how date is formatted when serialising to JSON. The name of the field should be serialised using default rules. That expresses what I'd like to do:
class ZonedDateTimeSerialiser : JsonSerializer<ZonedDateTime>() {
#Throws(IOException::class)
fun serialize(value: ZonedDateTime, gen: JsonGenerator, serializers: SerializerProvider?) {
val parseDate: String? = value.withZoneSameInstant(ZoneId.of("Europe/Warsaw"))
.withZoneSameLocal(ZoneOffset.UTC)
.format(DateTimeFormatter.ISO_DATE_TIME)
gen.writeString(parseDate)
}
}
data class OrderNotesRequest(
#JsonSerialize(using = ZonedDateTimeSerialiser::class)
val date: ZonedDateTime = ZonedDateTime.now()
)
But I get a type error:
Type mismatch.
Required:
KClass<out (JsonSerializer<Any!>..JsonSerializer<*>?)>
Found:
KClass<ZonedDateTimeSerialiser>
I did try switching the parameter to annotation to contentUsing but the type error remained the same.

Following is working for me
object JacksonRun {
#JvmStatic
fun main(args: Array<String>) {
val objMapper = ObjectMapper().apply {
registerModule(KotlinModule())
}
val order = OrderNotesRequest()
println(objMapper.writeValueAsString(order))
}
}
data class OrderNotesRequest(
#JsonSerialize(using = ZonedDateTimeSerialiser::class)
val date: ZonedDateTime = ZonedDateTime.now()
)
class ZonedDateTimeSerialiser : JsonSerializer<ZonedDateTime>() {
#Throws(IOException::class)
override fun serialize(value: ZonedDateTime, gen: JsonGenerator, serializers: SerializerProvider?) {
val parseDate: String = value.withZoneSameInstant(ZoneId.of("Europe/Warsaw"))
.withZoneSameLocal(ZoneOffset.UTC)
.format(DateTimeFormatter.ISO_DATE_TIME)
gen.writeString(parseDate)
}
}
build.gradle.kts:
dependencies {
implementation("com.fasterxml.jackson.core:jackson-core:2.13.2")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.13.2")
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.2")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0")
}
Gives me output:
{"date":"2022-03-21T10:29:19.381498Z"}
Do make sure you have the correct import for JsonSerializer
import com.fasterxml.jackson.databind.JsonSerializer
and add override marker to serialize method

Related

Why does Hibernate Validator ignore my custom annotations when written in Kotlin?

In a Kotlin project I am trying to get some Springboot Configuration properties JSR303 validated. Some custom validations seem to be necessary for me:
#Validated
#ConfigurationProperties("dtn")
class ExecutableAdapterConfiguration {
#FileFound // <-- custom
#Executable // <-- custom
lateinit var executable: Path
#NotEmpty
lateinit var user: String
#NotEmpty
lateinit var password: String
#NotEmpty
lateinit var productId: String
#NotEmpty
lateinit var version: String
}
#Constraint(validatedBy = [ExecutableValidator::class])
annotation class Executable(
val message: String = "Missing permissions to execute '${validatedValue}'",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class ExecutableValidator : ConstraintValidator<Executable, Path> {
override fun isValid(value: Path?, context: ConstraintValidatorContext): Boolean {
return value == null || !value.exists() || value.isExecutable()
}
}
#Constraint(validatedBy = [FileFoundValidator::class])
annotation class FileFound(
val message: String = "File '${validatedValue}' not found",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class FileFoundValidator : ConstraintValidator<Executable, Path> {
override fun isValid(value: Path?, context: ConstraintValidatorContext): Boolean {
return value != null && value.exists()
}
}
The path to the executable is supposed to be existing and, well, executable. When the Validator logic is tested (gradle bootRun, application.yaml points to dummy path), the two new validators are not executed. I was debugging into Hibernate Validator and when it lists found annotations, the custom ones are not added in the bean meta data definition. But when I do the same with a Java annotation definition, it is found and used.

How do I set and read properties in a SpringBoot application using Kotlin?

I'm a Java developer new to Kotlin and I'm trying to access values that I set in an application.yml file.
application.yml
q:
client:
apiUrl: https://app.abc.com/api/integr/v1.0
apiToken: abc
apiSecret: abc
authEmail: abc#example.com
sourceName: abc
This is my configuration class, which follows a similar pattern to Java.
#Component
#FeignClient(name = "q", url = "\${q.client.api-url}")
interface QClient {
#PostMapping("/user/get")
fun findUser(#RequestBody request: QRequest?):
QResponse<List<QUser?>?>
#PostMapping("/user/delete")
fun deleteUser(#RequestBody request: QRequest?): QResponse<DeleteResponse?>?
#Configuration
class QConfig {
#Value("\${q.client.apiToken}")
private val apiToken: String? = null
#Value("\${q.client.apiSecret}")
private val apiSecret: String? = null
#Value("\${q.client.authEmail}")
private val authEmail: String? = null
#Value("\${q.client.sourceName}")
private val sourceName: String? = null
fun createAuthRequest(): QAuth {
return QAuth(apiToken, apiSecret, authEmail, sourceName)
}
}
I don't want to assign null as default values for the instance variables, but Kotlin wants me to declare them like this to avoid null references.
I need to create an auth request and I'm calling the config class from the main class.
private fun generateRequest(email: String): QRequest {
val config = QClient.QConfig()
val auth = config.createAuthRequest()
return QRequest(auth, email)
}
But when debugging it just returns null values.
So after googling, I changed my approach and set all the key values into parameters of QConfig class like this:
#Configuration
class QConfig(
#Value("\${q.client.apiToken}") private val apiToken: String,
#Value("\${q.client.apiSecret}") private val apiSecret: String,
#Value("\${q.client.authEmail}") private val authEmail: String,
#Value("\${q.client.sourceName}") private val sourceName: String
) {
fun createAuthRequest(): QAuth {
return QAuth(apiToken, apiSecret, authEmail, sourceName)
}
}
The problem I faced here was it acts as a constructor and expects me to pass arguments while creating an instance for the QConfig class on the main class, which I wont have in the main class.
How can I get the values from the application.yml and access them as from instance variables?
You can use #ConfigurationProperties (ref)
#ConfigurationProperties("q.client")
#ConstructorBinding
data class ClientConfig(
val apiUrl: String, // variable name should be same as the one defined in application.yaml
val apiToken: String,
...other properties
)
#SpringBootApplication
#ConfigurationPropertiesScan
class SpringStackoverflowApplication {
#Autowired
private lateinit var clientConfig: ClientConfig
#EventListener(ApplicationReadyEvent::class)
fun doSomething() {
println("FOOBAR: $clientConfig")
}
}
fun main(args: Array<String>) {
runApplication<SpringStackoverflowApplication>(*args)
}
I solved this with Joffrey's reply, I used this format of config file
#Component
#Configuration
class QConfig {
#Value("\${q.client.apiToken}")
private val apiToken: String? = null
#Value("\${q.client.apiSecret}")
private val apiSecret: String? = null
#Value("\${q.client.authEmail}")
private val authEmail: String? = null
#Value("\${q.client.sourceName}")
private val sourceName: String? = null
fun createAuthRequest(): QAuth {
return QAuth(apiToken, apiSecret, authEmail, sourceName)
}
}
Then created the instance of QConfig like this on main class
#Autowired
val config = QConfig()
My bad, tried creating reference of class manually instead of using AutoWire. When started it pulled all the env variables passed on .yml file into the local variables.

#Valid annotation is not validating the list of child objects - Spring Boot + Kotlin

I have a very similar problem to this topic #Valid annotation is not validating the list of child objects but trying to implement using Kotlin.
The #Valid annotation is not working for WordDto. Only the class MessageDto is evaluated.
#SpringBootApplication
class ValidationsApplication
fun main(args: Array<String>) {
runApplication<ValidationsApplication>(*args)
}
data class WordDto(
#field:Max(5)
val word: String
)
data class MessageDto(
#Valid
#field:NotEmpty
val words: List<WordDto>
)
#RestController
class Controller {
#PostMapping("/hello")
fun hello(#Valid #RequestBody messageDto: MessageDto) {
messageDto.words.map(System.out::println)
}
}
I've tried this approach too:
val words: List<#Valid WordDto>
Also wrote this test that should be passing:
#Test
fun `should validate child objects`() {
val dto = MessageDto(
words = listOf(
WordDto("Long Word that should fail")
)
)
val violations = validator.validate(dto)
assertThat(violations).isNotEmpty()
}
Dependencies:
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
Any ideas of what could be missing?
The project can be found in Github
Following the #NikolaiShevchenko's answer, this is the solution.
data class MessageDto(
#field:Valid
#field:NotEmpty
val words: List<WordDto>
)

How to save field as JSON with Spring Data R2DBC and Postgres

There are any way to save some field of entity as Json with spring-data-r2dbc?
Example:
#Table("A")
class A {
#Id
var id: String = "1"
var some: MutableMap<String, String> = mutableMapOf()
}
And table:
create table A (
id varchar(255) not null primary key,
some jsonb
)
I've looked at Convertors of Spring data R2DBC, but It is necessery to write separate convertor for every class. Does it possible to generate converters to Json dynamically in runtime for all classes inherited from some special interface or annotated by specific annotation?
Thanks!
annotation class StoreJson
...
#Bean
fun converters() = R2dbcCustomConversions(
Reflections().getTypesAnnotatedWith(StoreJson::class.java).map { clz ->
mutableListOf(
#WritingConverter
object : GenericConverter {
override fun getConvertibleTypes() = setOf(GenericConverter.ConvertiblePair(clz, Json::class.java))
override fun convert(source: Any?, p1: TypeDescriptor, p2: TypeDescriptor) =
Json.of(objectMapper.writeValueAsString(source))
},
#ReadingConverter
object : GenericConverter {
override fun getConvertibleTypes() = setOf(GenericConverter.ConvertiblePair(Json::class.java, clz))
override fun convert(source: Any?, p1: TypeDescriptor, p2: TypeDescriptor) =
objectMapper.readValue((source as Json).asString(), clz)
}
)
}.flatten().toMutableList()
)

How to use Type converters for complex data type in ROOM library?

I'm new to ROOM library. I've some complex json data structure which i would like to store in ROOM database, i don't know how to use Type convertes for multiple list of objectes. Following are my Entities,
// Trying to put all my custom models in a single table
#Entity(tableName = "myTable")
data class RaceModelDatabase(
#PrimaryKey
val ID: String,
#Embedded val info: CustomModel,
#Embedded(prefix = "parti")
val parti: Map<String,UserModelDatabase> ,
#Embedded val totalTime: Map<String,TimeDataModel>
)
// Custom Models which also has Map objects
data class CustomModel (val name :String, val crdate : String )
data class UserModelDatabase(#Embedded val info : CustomModel,
#Embedded(prefix = "Result_") val result :Map<String,CustomModel>
)
data class TimeDataModel (
val Start : Long,
val End : Long
)
Here is an example of how to use TypeAdapter for one of your Map objects. You could follow the same for the rest.
class RaceTypeConverter {
#JvmStatic
#TypeConverter
fun fromString(value: String): Map<String, TimeDataModel > {
val mapType = object : TypeToken<Map<String, TimeDataModel >>() {}.type
return Gson().fromJson(value, mapType)
}
#TypeConverter
#JvmStatic
fun fromStringMap(map: Map<String, TimeDataModel>): String {
val gson = Gson()
return gson.toJson(map)
}
}

Resources