How to use #value annotation in kotlin data class - spring

I have an application.properties file like:
person-one-name=John
This is my data class, I have used #Value annotation outside dataclass.
#Value("\${person-one-name}")
lateinit var personOne: String
data class Person(val name: String, val age: Int) {
constructor(age: Int) : this(personOne, age)
}
I want to use the var personOne in my data class.
It gives an error lateinit property personOne has not been initialized

Following on from my comment under the Question:
data class Person(val name: String, val age: Int)
#Service
class PersonFactory(
#Value("\${person-one-name}")
private val personOne: String,
) {
fun createPerson(name: String? = null, age: Int) =
if (name != null) Person(name, age)
else Person(personOne, age)
}
Another gotcha, is that the PersonFactory service needs to be in a package at the same level or within the class that starts the App.
More info: https://springhow.com/a-guide-to-value-in-spring-boot/

Related

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.

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

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

No value passed for parameter

I use kotli. I define everything as per requirement, why I am getting
thing type of Issue
UserRegistrationService.kt: (25, 36): No value passed for parameter 'userRegistration'
I got this type of issue at my UserRegistration class No value passed
for parameter department, and userRegistration
I Creat ResponseTemplateVO POJO Class
ResponseVO.kt
package com.userservice.userregistration.VO
import com.userservice.userregistration.entity.UserRegistration
data class ResponseTemplateVO(
var userRegistration: UserRegistration,
var department: Department
)
Department.kt
package com.userservice.userregistration.VO
data class Department(
val departmentId:Long=-1,
val departmentName:String="",
val departmentAddress:String="",
val departmentCode:String=""
)
UserRegistration.kt
package com.userservice.userregistration.entity
data class UserRegistration(
val userId:Long=-1,
val firstName:String="",
val lastName:String="",
val email:String="",
val departmentId:Long=-1,
)
UserRegistrationService.kt
package com.userservice.userregistration.service
import com.userservice.userregistration.VO.Department
import com.userservice.userregistration.VO.ResponseTemplateVO
import com.userservice.userregistration.entity.UserRegistration
import com.userservice.userregistration.repository.UserRegistrationRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
#Service
class UserRegistrationService {
#Autowired
private lateinit var userRegistrationRepository: UserRegistrationRepository
#Autowired
private lateinit var restTemplate: RestTemplate
fun saveUserDetails(userRegistration: UserRegistration): UserRegistration {
return userRegistrationRepository.save(userRegistration)
}
fun getUserWithDepartment(userId: Long): ResponseTemplateVO {
val vo= ResponseTemplateVO()
val userRegistration:UserRegistration=userRegistrationRepository.findUserById(userId)
val department: Department? =
restTemplate.getForObject("http://localhost:9001/departments/"+userRegistration.departmentId,
Department::class.java)
vo.userRegistration=userRegistration
if (department != null) {
vo.department=department
}
return vo
}
}
I am getting error at this below method at the line 2
val vo= ResponseTemplateVO()
No value passed for parameter department and userRegistration .This is
the error
fun getUserWithDepartment(userId: Long): ResponseTemplateVO {
val vo= ResponseTemplateVO()
val userRegistration:UserRegistration=userRegistrationRepository.findUserById(userId)
val department: Department? =
restTemplate.getForObject("http://localhost:9001/departments/"+userRegistration.departmentId,
Department::class.java)
vo.userRegistration=userRegistration
if (department != null) {
vo.department=department
}
return vo
}
This declaration:
data class ResponseTemplateVO(
var userRegistration: UserRegistration,
var department: Department
)
packs multiple things:
it declares 2 properties userRegistration and department
it defines the primary constructor of the class with 2 arguments: userRegistration and department
When you write:
val vo = ResponseTemplateVO()
You're calling the constructor of that class, but you don't specify the 2 required arguments. You should instead call it by passing the arguments:
fun getUserWithDepartment(userId: Long): ResponseTemplateVO {
val userRegistration:UserRegistration=userRegistrationRepository.findUserById(userId)
val department: Department? = restTemplate.getForObject("http://localhost:9001/departments/"+userRegistration.departmentId,
Department::class.java)
if (department == null) {
// here you should decide if it should have a default value
// or throw an exception
}
return ResponseTemplateVO(userRegistration, department)
}
Note that you declared the department property as non-null, so you need a non-null department in order to create an instance of your class.
So if department is null you have 3 options:
throw an exception
use a default value instead
change the type of department in ResponseTemplateVO so it accepts nulls (Department? with ?)
Also, if you instantiate your class with all required value like that, and you don't need to modify its properties later, the properties can be declared val. This is usually more idiomatic Kotlin. With immutability, it's easier to reason about the values.
The issue is in the data class.
data class ResponseTemplateVO(
var userRegistration: UserRegistration,
var department: Department
)
Here you have added the following params into the constructor of the data class. Hence you will need to pass the values to the constructor of the class before you can initialise it.
Hence your ResponseTemplateVO data class will become like this
data class ResponseTemplateVO(
var userRegistration: UserRegistration?=null,
var department: Department?=null)
Now since we have already assigned null as the default value. Now you can initialise the data class and it creates the data class with the values set to null and you do not need to pass any value for params to the constructor. Now you can access each of the variables and set the respective data into them.

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)
}
}

Spring data mongodb and scala lazy val: do not serialise bitmap$0 field

I use spring boot data mongodb in my scala project. When saving a case class that contains a lazy val, an additional bitmap$0 field appears in the mongo document (even if lazy val field is annotated with org.springframework.data.annotation.Transient). For example:
case class User(val firstName: String, val lastName: String) {
#Transient lazy val fullName: String = firstName +' ' + lastName
}
trait UserMongoRepository extends MongoRepository[User, String]
#Service
class userService(userMongoRepository: UserMongoRepository){
def saveUser = {
val u = User("Bob", "Marley")
userMongoRepository.save(u)
}
}
This results in a document:
{"firstName": "Bob", "lastName": "Marley", "bitmap$0": false}
How do I save a class with lazy val without the bitmap$0 field?

Resources