Spring Boot REST API - Deserialise a query attribute with square braces with Jackson - spring-boot

i am calling a legacy rest endpoint. There i have to deserialise a query parameter in a rest endpoint with square braces author[name].
Question: Is it possible to deserialise the attribute author name into the AuthorDto(spring boot + kotlin)?
import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.JsonProperty
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
#RestController
class AuthorController {
#GetMapping("/author/{id}")
fun getAuthorFromLegacyApi(
#PathVariable("id") id: Long,
authorDto: AuthorDto?
) = ResponseEntity.ok(authorDto)
}
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
data class AuthorDto(
#RequestParam(name = "author[name]")
val name: String?,
// long list of different query parameters
val country: String? = null,
)
code: https://github.com/nusmanov/spring-boot-validation-hibernate/blob/main/src/main/kotlin/com/example/bookvalidation/author/AuthorController.kt
to test: GET http://localhost:8080/author/5?author[name]=Joe
there is a junit test see above

Yes, it is possible, if you annotate authorDto with #RequestParam("author[name]"):
#GetMapping("/author/{id}")
fun getAuthorFromLegacyApi(#PathVariable("id") id: Long, #RequestParam("author[name]") authorDto: AuthorDto) = ResponseEntity.ok(authorDto.name)

How about using the map.
#GetMapping("/author/{id}")
fun getAuthorFromLegacyApi(#PathVariable("id") id: Long, #RequestParam authorDto: Map<String, String>): String {
return "Hello $id ${authorDto["author[name]"]} ${authorDto["currency"]}"
}
I tried with author/5?author%5Bname%5D=Tom&currency=INR got the response of Hello 5 Tom INR

Related

How to handle this Type MisMatch found List required Entity spring Boot JPA

I create a some operations in my controller class ,I want to store
the results what i get from the operation but when I store these list
of things it show me an error like Below
Type mismatch.
Required:
DepositMaterial!
Found:
List<Result>
Here is my controller class
#PatchMapping("/pendingValue")
fun pend(#ModelAttribute request:ReqFindPending):ResponseEntity<*>{
val existingWithdraw = depositRepository.findPendingByWithDrawId(request.withDrawId)
if (existingWithdraw != null){
val upPending = depositRepository.getPending(withDrawId = request.withDrawId)
depositRepository.save(upPending)
return ResponseEntity(ResMessage("Pending update successfull"),HttpStatus.OK)
}else{
return ResponseEntity(ResMessage(" id getting null value"),HttpStatus.NOT_ACCEPTABLE)
}
}
My repository
package com.nilmani.workload.repository
import com.nilmani.workload.entity.DepositMaterial
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.RequestParam
interface DepositRepository : JpaRepository<DepositMaterial, Long> {
#Query("SELECT wm.quantity ,dd.totalDeposit,wm.quantity -dd.totalDeposit AS pending FROM WithdrawMaterial wm INNER JOIN DepositMaterial dd ON wm.id = dd.withDrawId ")
fun getPending(#Param("withDrawId")withDrawId: Long?):List<Result>
}
Here is my result Model
data class Result(
val pending: Long,
val quantity: Long,
val totalDeposit: Long
)
DepositMaterial Entity Class
package com.nilmani.workload.entity
import com.nilmani.workload.enum.Material
import java.time.LocalDateTime
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
#Entity
data class DepositMaterial (
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
val id:Long=-1,
val withDrawId:Long=-1,
val totalDeposit:Long=-1,
val material:Int= Material.NONE.type,
val depositTime:LocalDateTime = LocalDateTime.now(),
val pending:Long = -1,
val isDeposited:Boolean=false,
)
What is the reason for this issue,I want to only return these three
things, and store the result subtraction result of totalDeposit and
quantity in pending column to update the table but , It give me error
to store the depositMaterial
You are returning a list of Results from getPending, not an individual DepositMaterial. But the save method requires a single object to save. Try only requesting one object from getPending by changing the return signature.
depositRepository.getPending
returns a list of Entities.
depositRepository.save(upPending)
takes a single Entity to save in the database.
Solution:
Either change your save(upPending) method to saveAll(upPending), or update your getPending repo method to return a unique Entity object.

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.

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

spring validation in composition

I have the following data classes:
data class User (
#field:NotEmpty
val firstName: String?
#field:NotEmpty
val lastName: String?
)
data class Expert (
#field:NotEmpty
val name: String?
#field:NotNull
val contact: User?
)
And I would like to use my rest API endpoint to create an expert with spring validation:
#RestController
#RequestMapping("/api/experts")
class ExpertController(private val expertService: ExpertService) {
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
fun create(#Valid #RequestBody dto: Expert) = expertService.create(dto)
}
Validation on name and contact fields works fine. But validation on firstName and lastName fields (User class) doesn't work. Is it a normal behaviour? I can't use validation on composition? Why? Or am I missing something?
In order for the User to be validated if it is contained within an Expert, you will need to add the #Valid annotation to it, so Spring's Validator knows to keep checking, otherwise it will stop.
Try this (untested):
data class Expert (
#field:NotEmpty
val name: String?
#field:NotNull
#field:Valid
val contact: User?
)

Kotlin Spring boot #ConfigurationProperties for list

I want to read the yaml config files using Kotlin and below is my code:
application.yml
message:
messages:
- name: abc
type: aaa
size: 10
- name: xyz
type: bbb
size: 20
MessageConfig.kt
package com.example.demokotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
import java.math.BigDecimal
#ConfigurationProperties(prefix = "message")
#Configuration
class MessageConfig {
lateinit var messages: List<Message>
}
class Message {
lateinit var name: String
lateinit var type: String
lateinit var size: BigDecimal
}
Class to use the config:
package com.example.demokotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
#Component
class MessageService #Autowired constructor(private var messageConfig: MessageConfig) {
fun createMessage(): String {
println("in service........")
println(messageConfig.messages[0].name)
println(messageConfig.messages[0].type)
println(messageConfig.messages[0].size)
return "create a message......."
}
}
Looks like if the yaml file has array/list, Kotlin can't read it properly but it works without array.
I have exactly the same code and works for Java. Something wrong with my Kotlin code?
You are encountering this bug. Simply changing
lateinit var messages: List<Message>
to
var messages: MutableList<Message> = mutableListOf()
makes your code work. Here is a full working example.
edit (March 2019):
As of SB 2.0.0.RC1 and Kotlin 1.2.20, you can use lateinit or a nullable var.
Docs
edit (May 2020):
As of SB 2.2.0 you can use #ConstructorBinding along with #ConfigurationProperties to set val properties on a data class.
Using the original class as an example, you can now write it like so:
#ConstructorBinding
#ConfigurationProperties(prefix = "message")
data class MessageConfig(val messages: List<Message>) {
data class Message(
val name: String,
val type: String,
val size: BigDecimal
)
}
Is Fixed in Kotlin 1.3.11 with spring-boot 2.10, sample provided in MessageConfig.kt works now
#PropertySource("classpath:application.yml")
#ConfigurationProperties(value = "message")
class MessageConfig {
lateinit var messages: List<Message>
}
class Message {
lateinit var name: String
lateinit var type: String
lateinit var size: BigDecimal
}

Resources