how to serialize String/null as json in quarkus? - jackson

quarkus serialize String as plain string, null as empty body(with http code 204)
"foo" -> foo
null -> (empty body)
how to make it serialize String and null as json like:
"foo" -> "foo"
null -> null

You could try somhthing like this:
package org.acme.config;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/greeting")
public class GreetingResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public String greetirng() {
return "{\"greeting\": \"Hello, JSON\", \"penalty\": null}";
}
}
Check it:
$ curl localhost:8080/greeting
{"greeting": "Hello, JSON", "penalty": null}

You should use the ObjectMapper class for serializing/deserialising process in Quarkus.
ObjectMapper provides functionality for reading and writing JSON, either to and from basic POJOs (Plain Old Java Objects)
First, you must be sure the io.quarkus:quarkus-jackson package exists in your build.gradle file. If it doesn't exist you can install it with this command:
./gradlew addExtension --extensions="io.quarkus:quarkus-jackson"
After that, you must get an instance:
ObjectMapper objectMapper = new ObjectMapper();
You will find your need in this instance of ObjectMapper.

Related

Ktor - create List from Json file

i am getting error - This class does not have constructor at object : TypeToken<List<Todo>>() + object is not abstract and does not implement object member
data class Todo(
val identifier: Long ,
val name: String ,
val description: String
)
class DefaultData {
private lateinit var myService: MyService
#PostConstruct
fun initializeDefault() {
val fileContent = this::class.java.classLoader.getResource("example.json").readText()
val todos: List<Todo> = Gson().fromJson(fileContent, object : TypeToken<List<Todo>>() {}.type)
myService.createTodoFromJsontodos
}
}
how can I fix this?
Objective is : To be able to create an endpoint that can get data from json file via service
Is there is a full fledged example
Also how to create interfaces in Ktor? As I want to use Dependency Inversion to enable retrieving data from different sources
Kotlin has built-in util similar to TypeToken, so I suggest using it instead:
Gson().fromJson(fileContent, typeOf<List<Todo>>().javaType)
You will need to add a dependency to kotlin-reflect. typeOf() function is marked as experimental, but I use it for some time already and never had any problems with it.
Also, you said in your comment that this is a starter project. If you don't have any existing code already then I suggest to use kotlinx-serialization instead of Gson. It is a de facto standard in Kotlin.
You can easily take advantage of kotlinx-serialization.
Steps:
Add the kotlin serialization plugin in your build.gradle file
kotlin("plugin.serialization") version "1.5.20"
plugins {
application
java
id("org.jetbrains.kotlin.jvm") version "1.5.21"
kotlin("plugin.serialization") version "1.5.20"
}
Add the dependecy for serialization library
dependencies {
...
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
}
Decode your json string to corresponding object using Json decode method
val JSON = Json {isLenient = true}
val mytodos = JSON.decodeFromString(message) as List<Todo>

Ktor with Kmongo and kotlinx.serialization

I have a setup where I use KTor with KMongo and Kotlinx.Serialization.
The Kmongo part works, I can get and put my Class
#Serializable
data class Task(#ContextualSerialization #SerialName("_id") val _id : Id<Task> = newId(),
val title : String = "",
val description : String = ""
)
Into the database and retrieve it. That all works flawlessly.
But when I try to send that object through a rest call to the frontend, again with Kotlinx.Serialization.
get<Tasks>{ task ->
val dao by di().instance<Dao>();
val task = Task( title = "task1", description = "task1description");
val foundTask = dao.read(task);
if(foundTask != null){
call.respond(foundTask)
} else {
call.respond("didn't find anything")
}
}
It throws this expection:
kotlinx.serialization.SerializationException: Can't locate argument-less serializer for class WrappedObjectId. For generic classes, such as lists, please provide serializer explicitly.
at kotlinx.serialization.PlatformUtilsKt.serializer(PlatformUtils.kt:21)
at kotlinx.serialization.modules.SerialModuleExtensionsKt.getContextualOrDefault(SerialModuleExtensions.kt:29)
at kotlinx.serialization.ContextSerializer.serialize(ContextSerializer.kt:29)
at kotlinx.serialization.json.internal.StreamingJsonOutput.encodeSerializableValue(StreamingJsonOutput.kt:227)
at kotlinx.serialization.builtins.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:72)
Now I figured out that this is because there are 2 instances of the kotlin.serialization json. and the one on KMongo does not share it's serializers with the other one.
so I added the serializers from KMongo to the other instance from Ktor
install(ContentNegotiation) {
json(
json = Json(DefaultJsonConfiguration.copy(prettyPrint = true), context = kmongoSerializationModule),
contentType = ContentType.Application.Json
)
}
and now I get
java.lang.ClassCastException: class kotlinx.serialization.json.internal.StreamingJsonOutput cannot be cast to class com.github.jershell.kbson.BsonEncoder (kotlinx.serialization.json.internal.StreamingJsonOutput and com.github.jershell.kbson.BsonEncoder are in unnamed module of loader 'app')
So my question is why is it happening and how to fix it?
Edit: This issue has been fixed.
I also posted this question on the KMongo github and basically got an instant response that this was fixed for Jackson but not yet for Kotlinx.serialization.
Someone is going to fix this.
Edit: It has been fixed

spring data rest kotlin associations POST

I followed the tutorial http://www.baeldung.com/spring-data-rest-relationships.
I also observed that I can create the association directly by providing the link to the relationship.
curl -i -X POST -H "Content-Type:application/json" -d '{"name":"My Library"}' http://localhost:8080/libraries
curl -i -X POST -d '{"title":"Books", "library":"http://localhost:8080/libraries/1"}' -H "Content-Type:application/json" http://localhost:8080/books
This works fine in Java and also in Kotlin when using a regular class.
However, if I use a data class in Kotlin, I get the following error
2018-04-26 14:13:43.730 ERROR 79256 --- [nio-8080-exec-2] b.e.h.RestResponseEntityExceptionHandler : org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of com.baeldung.models.Library (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/libraries/1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.baeldung.models.Library (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/libraries/1') at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 29] (through reference chain: com.baeldung.models.Book["library"])
I do have the relevant kotlin-spring, kotlin-jpa and kotlin-noarg plugins in my project.
Code is here https://github.com/vijaysl/spring-data-rest
Try adding #JsonCreator(mode = JsonCreator.Mode.DISABLED) annotation on primary constructor. No need to disable the com.fasterxml.jackson.module:jackson-module-kotlin.
Explanation:
Kotlin Jackson module implies your default constructor is the JSON creator (see KotlinValueInstantiator class).
Therefore, Spring Data REST does not apply its bean deserializer modifier (that is supposed to load a bean by URI) because bean properties mappings are not used for creator properties (constructor params).
KotlinValueInstantiator tries to deserialize constructor params using standard deserializers and instantiators and this leads to the error you mentioned.
Possible solution:
Since koltin-jpa module adds a default empty constructor for JPA, you can instruct Jackson not to use the JSON creator but the default empty constructor by explicitly disabling it.
Example:
#Entity
class Book #JsonCreator(mode = JsonCreator.Mode.DISABLED) constructor(
#ManyToMany
val libraries: ModifiableList<Library> = ArrayList(),
): AbstractPersistable<Long>(), Identifiable<Long>
Kotlin data classes are pretty strict. It's telling you, basically, it can't construct your POKO and it's listing some of the ways it tries. One of them is with a String constructor. Others are through private field manipulation (which is the way it's been done normally).
Data classes in kotlin, if they have fields declared as private val name:String translate to (in java) private final String name; It can't assign to a final field (which is dirty to try to assign to a private field, but impossible when it's final; the JVM won't allow it) and there are no getName() or setName() functions which can be used as another method of hydration.
Some options:
Declare your variables are var instead of val. private var name:String is java equiavalent to private String name which will use field based (dirty) hydration.
include a specific kotlin dependency for kotlin that fixes this issue: compile("com.fasterxml.jackson.module:jackson-module-kotlin") have a look at this project
example kotlin class that should work for you:
import org.springframework.hateoas.Identifiable
import java.time.LocalDate
import javax.persistence.*
import javax.validation.constraints.*
#Entity
data class Employee(#Pattern(regexp = "[A-Za-z0-9]+")
#Size(min = 6, max = 32)
val name: String,
#Email
#NotNull
val email: String?,
#PastOrPresent
val hireDate: LocalDate = LocalDate.now(),
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL])
val forms:List<Form> = listOf(),
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL])
val reports:List<Report> = listOf(),
#Id #GeneratedValue( strategy = GenerationType.IDENTITY) private val id: Long? = null): Identifiable<Long> {
override fun getId() = id
constructor(name:String): this(name,"$name#foo.com")
}
With kotlin all Ok.
Just replace "data class" to "class".
Jackson don't find empty constructor in "data class". And use other deserializator... not Uri....

Jackson YAML: parsing anchors and references

This is a follow-up question on Jackson YAML: support for anchors and references:
I had a tough time understanding how Jackson treats references (since they don't exist natively in JSON), so I wrote a test:
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import org.junit.Test
import org.yaml.snakeyaml.Yaml
import kotlin.test.assertEquals
class YamlRefTest {
data class Item(val name: String)
data class Config(val availableItems: List<Item>,
val selectedItems: List<Item>)
#Test
fun testReferenceSnakeYaml() {
val parser = Yaml()
val test = (parser.load(testYaml)
as Map<String, Map<String, List<Map<String, String>>>>)["Config"]!!
assertEquals("test", test["availableItems"]!![0]["name"])
assertEquals("test", test["selectedItems"]!![0]["name"])
}
#Test
fun testReferenceJackson() {
val mapper = ObjectMapper(YAMLFactory())
.registerModule(KotlinModule())
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true)
val config = mapper.readValue(testYaml, Config::class.java)
assertEquals(1, config.selectedItems.size)
assertEquals("test", config.selectedItems[0].name)
}
companion object {
val testYaml = """
Config:
availableItems:
- &a1
name: test
- &a2
name: test2
selectedItems:
- *a1
"""
}
}
The first test passes, the second doesn't. From my understanding of the spec, if you just use anchors (without the merge syntax), YAML treats references pretty much as copy and paste. Why isn't this possible with Jackson? And if it is, what do I need to do to make this work?
This currently fails with:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `undagen.YamlRefTest$Item` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('a1')
at [Source: (StringReader); line: 9, column: 9] (through reference chain: undagen.YamlRefTest$Config["selectedItems"]->java.util.ArrayList[0])
Note: Answers involving the correct use of #JsonIdentityInfo are helpful (I can't seem to grok how this translates to anchors).

Exception when using spring-data-mongodb with Kotlin

I'm new to Kotlin, and experimenting with spring-data-mongodb. Please see example below (also available here as fully runnable Maven project with in-memory MongoDb: https://github.com/danielsindahl/spring-boot-kotlin-example).
Application.kt
package dsitest
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
#SpringBootApplication
open class Application
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
User.kt
package dsitest
import org.springframework.data.annotation.Id
import org.springframework.data.annotation.PersistenceConstructor
import org.springframework.data.mongodb.core.mapping.Document
#Document(collection = "user")
data class User #PersistenceConstructor constructor(#Id val id: String? = null, val userName: String)
UserRepository.kt
package dsitest
import org.springframework.data.repository.CrudRepository
interface UserRepository : CrudRepository<User, String>
KotlinIntegrationTest.kt
package dsitest
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner
#RunWith(SpringRunner::class)
#SpringBootTest
class KotlinIntegrationTest constructor () {
#Autowired
lateinit var userRepository : UserRepository;
#Test
fun persistenceTest() {
val user : User = User(userName = "Mary")
val savedUser = userRepository.save(user)
val loadedUser = userRepository.findOne(savedUser.id) // Failing code
println("loadedUser = ${loadedUser}")
}
}
When running the test KotlinIntegrationTest.persistenceTest, I get the following error message when trying to retrieve a User object from MongoDb:
org.springframework.data.mapping.model.MappingException: No property null found on entity class dsitest.User to bind constructor parameter to!
If I modify the User data class so that userName is nullable, everything works.
data class User #PersistenceConstructor constructor(#Id val id: String? = null,
val userName: String? = null)
I would like to understand why this is the case, since I don't want userName to be nullable. Is there some alternative, better way of defining my User class in Kotlin?
Many thanks,
Daniel
Yes, it is a known problem. You should check how the bytecode for your User class looks like. Java sees the constructor with all the parameters present and tries to call it with a null value for the 2nd one.
What you could do is to try adding #JvmOverloads to your constructor - this will force Kotlin compiler to generate all versions of the constructor and so the Spring Data Mongo could pick the correct one (get rid of the #PersistenceConstructor) then.
You could also define 1 constructor with no defaults - only for Java-based frameworks and 2nd one with some defaults your you. Or...
When I write things like you are now, I create simple 'persistence' data classes with no default values whatsoever that are mapped to/from my regular domain objects (a sort of abstraction over database). It may generate some overhead at the start - but keeping your domain model not coupled so tightly with the storage model is usually a good idea anyway.