JSON key is missing (using #JsonComponent on Spring-boot with kotlin) - spring-boot

Thanks reading this question.
this problem confused me.
I created code that response JSON data like below.
#RestController
class JsonTestController {
#GetMapping("jsonTest")
fun jsonTest(): ResponseEntity<HaveBoolean> {
val value = BooleanValue(true)
return ResponseEntity.ok(HaveBoolean(value))
}
data class BooleanValue(val value: Boolean)
data class HaveBoolean(
val isAdmin: BooleanValue,
)
}
and #JsonComponent is below.
#JsonComponent
class BooleanValueJson {
class Serializer : JsonSerializer<JsonTestController.BooleanValue>() {
override fun serialize(value: JsonTestController.BooleanValue, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeBoolean(value.value)
}
}
class Deserializer : JsonDeserializer<JsonTestController.BooleanValue>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): JsonTestController.BooleanValue =
JsonTestController.BooleanValue(p.valueAsBoolean)
}
}
When I request localhost://8082/jsonTest, I got empty json ({}).
but, I tried other variable name like hoge, mean coding like below.
data class HaveBoolean(
val hoge: BooleanValue,
)
then, I request again, I can get correctly json ({"hoge": true}).
Can't I use isAdmin name on data class ?
Do you have any idea why this problem is happening?
thanks.

This is a known issue with jackson in kotlin. Jackson basically tries to remove is from the name but kotlin data class implementation doesn't have a proper getter without "is" resulting in mismatch. You can add JsonProperty("isAdmin") to the variable and it should work.
data class HaveBoolean(
#get:JsonProperty("isAdmin")
val isAdmin: BooleanValue,
)

Related

spring json serialization issue

I am unable to get is_secure object attribute in json response, what is wrong with this code ?
#Configuration
class RouterConfiguration( ) {
#Bean
fun testRoutes(testHandler: TestHandler) = coRouter {
GET("/test", testHandler::testFunction)
}
}
data class TestClass(
val is_secure: Int? = 1,
val xyz: String?
)
#Component
class TestHandler{
suspend fun testFunction(request: ServerRequest): ServerResponse =
ServerResponse.ok().bodyValueAndAwait(TestClass(1,"abc"))
}
is prefixed fields (with camelCase or snake_case pattern) are only serialized if they are of type Boolean. You can find more details about it here.
If you wish to keep the is prefix, you may do so by using #get use-site target. Just use #get:JsonProperty("is_secure") on the is_secure field and it should do.

Inserting Post Method with Spring Boot

I'm learning Kotlin, part of my project is to integrate JSON as an object and use the POST method to change or add information.
I'm not able to do this, I need help.
package com.example.blog
import org.springframework.web.bind.annotation.*
data class Relatorio(
val titulo: String,
val autor: String,
val serie: String
)
#RestController
#RequestMapping("/Bradesco")
class BradescoController {
#GetMapping()
public fun relatorio(): Relatorio {
val result = Relatorio(
"Investimentos",
"Luis Felipe",
"Bradesco Analises"
)
return result
}
#PostMapping
#RequestMapping( #RequestBody "/empiricus")
public fun relatorio2() {
"titulo" = "Contra as altas taxas"
return "Atualizado";
}
}
It looks like some annotations are out of place in your relatorio2 method. You want to register a REST-endpoint for the POST-method and the path /empiricus.
This can happen one of two ways:
Annotate the method with #RequestMapping(value = "/empiricus", method = RequestMethod.POST)
Annotate the method with `#PostMapping("/empiricus") (you can omit the method-parameter from the example above, since this a shortcut for exactly that.
The #RequestBody annotation needs to be placed in the parameter of the relatorio2 method since it tells Spring to map the POST request-body to an object.
Therefore the method should look something like this:
#PostMapping("/empiricus")
public fun relatorio2(#RequestBody relatorio: Relatorio) {
"titulo" = "Contra as altas taxas"
return "Atualizado";
}
Since you added a path on class level, the complete path to call the method is /Bradesco/empiricus. When the object is available in the relatorio2 method, you can use it in your business logic.

Spring Jackson Databind does not work for my Kotlin data class

When using Spring's RestTemplate to deserialize some JSON response into an object I fail to do so because I use a Kotlin data class as my object model.
This is the data class:
data class Description (
val descriptionShort: String,
val descriptionLong: String,
val productGroupName: String,
val shortDescriptionProductGroup: String,
val descriptionProductGroupMarketing: String
)
I using these dependencies:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux")
//others
}
dependencyManagement {
imports {
mavenBom("org.springframework.boot:spring-boot-dependencies:2.2.0.RELEASE")
//others
}
dependencies {
dependency("org.springframework.cloud:spring-cloud-stream-reactive:2.2.1.RELEASE")
dependency("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2")
//others
}
}
The error message when executing unit tests that involves the RestTemplate logic:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.company.importer.customer.converter.ut.Description (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
A friend told me a no-arg constructor is needed.
This is done in Kotlin by giving every property a default value:
data class Description (
val descriptionShort: String = "",
val descriptionLong: String = "",
val productGroupName: String = "",
val shortDescriptionProductGroup: String = "",
val descriptionProductGroupMarketing: String = ""
)
I faced a similar problem many days ago; as first step, I solved the problem as in the response by #xetra11, but I was not very happy with the idea of having default values for technical reasons only.
Finally I solved my issue simply adding the #RequestBody annotation to the controller's method parameter: my method now looks like
fun newTransaction(#RequestBody input: NewTxRequest)
NewTxRequest is defined as follows
data class NewTxRequest(val from: String, val to: String, val amount: BigDecimal)
and the serialization works fine... I hope this can help you, too!

Spring Boot and Kotlin - How to validate a JSON object

I'm using Spring Boot in Kotlin.
I'm taking in some JSON string, parsing it with ObjectMapper however I want to validate it has everything as in per the model - namely id and s3FilePath are not blank or missing.
So this is the model I want to validate against:
#JsonIgnoreProperties(ignoreUnknown = true)
class MyModel {
var id : String = ""
var s3FilePath : String = ""
}
This is where I use that model:
class FirstMessage {
fun create(newMessage: String) : String {
val objectMapper = ObjectMapper()
val parsedMap : MyModel = objectMapper.readValue(newMessage, MyModel::class.java)
val result = MyModel()
result.id = parsedMap.id
result.s3FilePath = parsedMap.s3FilePath
return objectMapper.writeValueAsString(result)
}
}
And finally I have this test where I want to validate an exception:
#Test
fun incompleteDataReturnsException() {
var input = """{"missing": "parts"}"""
// FirstMessage().create(input) // Will make some assertion here here
}
Any help would be appreciated. I've just started using Spring and its pretty 'intense'.
Thanks.
p.s. If creating that model wrong/there's a better way, please let me know. I'm a little unsure if thats the correct way.
You should use data classes for the models. Also, use kotlin jacksonObjectMapper() instead of ObjectMapper(). Standard ObjectMapper will not work in Kotlin. Or inject ObjectMapper from Spring context. Add "com.fasterxml.jackson.module:jackson-module-kotlin" in your dependencies.
#JsonIgnoreProperties(ignoreUnknown = true)
data class MyModel (
val id : String,
val s3FilePath : String
)
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
class FirstMessage {
fun create(newMessage: String) : String {
val parsedMap : MyModel = jacksonObjectMapper().readValue(newMessage)
return jacksonObjectMapper().writeValueAsString(parsedMap)
}
}
class FirstMessageTest {
#Test
fun incompleteDataReturnsException() {
val input = """{"missing": "parts"}"""
assertThrows (MissingKotlinParameterException::class.java
{FirstMessage().create(input)} // Will make some assertion here here
}
#Test
fun `Should parse`() {
val input = """{"id":"id",
"missing": "parts",
"s3FilePath":"somePath"}"""
FirstMessage().create(input) // Will make some assertion here here
}
}
If I understood your question correct, you just want to check if your required properties are set. So I would suggest checking for that properties after you parsed the string with something like this:
class FirstMessage {
fun create(newMessage: String) : String {
val objectMapper = ObjectMapper()
// validation 1: your input is valid JSON
val parsedMap : MyModel = objectMapper.readValue(newMessage, MyModel::class.java)
// validation 2: check that your properties are set
if(parsedMap.id.isNullOrEmpty() ||
parsedMap.s3FilePath.isNullOrEmpty())
{
throw IllegalArgumentException("Invalid input")
}
val result = MyModel()
result.id = parsedMap.id
result.s3FilePath = parsedMap.s3FilePath
return objectMapper.writeValueAsString(result)
}
}
Depending of the scope, a nicer solution would be a new annotation like #NotEmpty that you set on the properties of your target class that are required and have a generic parser function which validates all the annotated fields on your parsed object and throws a better exception which says exactly which fields are missing.

How to deserialize WWW form field to enum in Spring?

I'm facing issue when trying deserialize input coming from WWW form to enum class in Spring application, in Kotlin.
My DTO and enum classes:
enum class Status(#get:JsonValue val value: Int) {
NORMAL(0),
ERROR(1);
companion object {
#JvmStatic
#JsonCreator
fun of(number: Int?): Status? {
return values().find { it.value == number }
}
}
}
data class RequestData(val status: Status?)
Controller's POST request receiver method:
#PostMapping("/post")
fun register(#Valid data: RequestData, error: Errors) {}
When I make POST request with status = 0 using Postman, request's failing with following exception.
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.example.Controller.post, parameter data
When I make request with status = NORMAL then no exception, but that what I don't want.
I'm using application/x-www-form-urlencoded content type in POST request.
Please let me know where I'm doing wrong.
You may use Converter class for that. Note that request parameters my look like a numbers to you, but they are in fact strings. That's why converter below accepts String? and returns Status?. That means, it would be convenient for you if your enum accept it as well. Example: NORMAL("0"), ERROR("1").
class ConvStringToStatus : Converter<String?, Status?> {
override fun convert(source: String?) = Status.of(source)
}
To make it work, converter must be registered as below.
#Configuration
class WebConfig : WebMvcConfigurer {
override fun addFormatters(registry: FormatterRegistry) {
registry.addConverter(ConvStringToStatus())
}
}
You just use the name.
ex) status = NORMAL
And does value matter? You can use ordinal
enum class Status{
NORMAL,
ERROR,
}
println(NORMAL.ordinal)
//result:0
This is a joke, but if you need
enum class Status(val value:String){
`0`("NORMAL"),`1`("ERROR")
}
println(data.status.value)
For anyone still looking and finding this. The example code works and does not throw non-null field is null exception if you add jackson-module-kotlin dependency. It should match your com.fasterxml.jackson.core version
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>2.9.8</version>
</dependency>

Resources