spring json serialization issue - spring

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.

Related

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

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

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.

Deserializing classes with lazy properties using Gson and Kotlin 1.0 beta 4

Using Gson, I want to deserialize a Kotlin class that contains a lazy property.
With Kotlin 1.0 beta 4 I get the following error during object deserialization:
Caused by: java.lang.InstantiationException: can't instantiate class kotlin.Lazy
With Kotlin 1.0 beta 2, I used to mark the property with the #Transient annotaiton to tell Gson to skip it. With beta 4 this is not possible anymore, as the annotation causes a compile error.
This annotation is not applicable to target 'member property without backing field'
I can’t figure out how to fix this. Any ideas?
Edit: the lazy property is serialized to JSON ("my_lazy_prop$delegate":{}), but this is not what I want as it is computed from other properties. I suppose if I find a way to prevent the property from being serialized the deserialization crash would be fixed.
Since Kotlin 1.0 simply mark the field like this to ignore it during de/serialization:
#delegate:Transient
val field by lazy { ... }
The reason is that the delegate field is not a backing field actually so it was forbidden. One of the workarounds is to implement ExclusionStrategy: https://stackoverflow.com/a/27986860/1460833
Something like that:
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
annotation class GsonTransient
object TransientExclusionStrategy : ExclusionStrategy {
override fun shouldSkipClass(type: Class<*>): Boolean = false
override fun shouldSkipField(f: FieldAttributes): Boolean =
f.getAnnotation(GsonTransient::class.java) != null
|| f.name.endsWith("\$delegate")
}
fun gson() = GsonBuilder()
.setExclusionStrategies(TransientExclusionStrategy)
.create()
See related ticket https://youtrack.jetbrains.com/issue/KT-10502
The other workaround is to serialize lazy values as well:
object SDForLazy : JsonSerializer<Lazy<*>>, JsonDeserializer<Lazy<*>> {
override fun serialize(src: Lazy<*>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement =
context.serialize(src.value)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Lazy<*> =
lazyOf<Any?>(context.deserialize(json, (typeOfT as ParameterizedType).actualTypeArguments[0]))
}
class KotlinNamingPolicy(val delegate: FieldNamingStrategy = FieldNamingPolicy.IDENTITY) : FieldNamingStrategy {
override fun translateName(f: Field): String =
delegate.translateName(f).removeSuffix("\$delegate")
}
Usage example:
data class C(val o: Int) {
val f by lazy { 1 }
}
fun main(args: Array<String>) {
val gson = GsonBuilder()
.registerTypeAdapter(Lazy::class.java, SDForLazy)
.setFieldNamingStrategy(KotlinNamingPolicy())
.create()
val s = gson.toJson(C(0))
println(s)
val c = gson.fromJson(s, C::class.java)
println(c)
println(c.f)
}
that will produce the following output:
{"f":1,"o":0}
C(o=0)
1
As explained by other answers, the delegate field should not be serialized.
You can achieve this with transient in the delegate field, as proposed by #Fabian Zeindl:
#delegate:Transient
val field by lazy { ... }
or skipping all delegate fields in the GsonBuilder, as proposed by #Sergey Mashkov:
GsonBuilder().setExclusionStrategies(object : ExclusionStrategy {
override fun shouldSkipClass(type: Class<*>): Boolean = false
override fun shouldSkipField(f: FieldAttributes): Boolean = f.name.endsWith("\$delegate")
}
However, you may face a NullPointerException if your class doesn't have a no-argument constructor.
It happens because when Gson doesn't find the no-argument constructor, it will use a ObjectConstructor with an UnsafeAllocator using Reflection to construct your object. (see https://stackoverflow.com/a/18645370). This will erase the Kotlin creation of the delegate field.
To fix it, either create a no-argument constructor in your class, or use Gson InstanceCreator to provide Gson with a default object.
GsonBuilder().registerTypeAdapter(YourClass::class, object : InstanceCreator<YourClass> {
override fun createInstance(type: Type?) = YourClass("defaultValue")
})

Resources