I'm looking to rewrite a spring javafx application in Kotlin using Ktor. I am new to both Spring and Ktor.
I'm having trouble using Ktor to figure out how to approach serialization and deserialisation of POJOs. (These POJOs are part of a common library).
The API I'm trying to serve requests to with Ktor uses:
Hibernate ORM as ORM mapper
Elide with RSQL filters to serve JSON-API conform data
For example "data/event" would return:
{
"data": [
{
"type": "event",
"id": "15b6c19a-6084-4e82-ada9-6c30e282191f",
"attributes": {
"imageUrl": null,
"name": "some text",
"type": "NUMERIC"
}
}, // and more event objects
]
}
Looking at the codebase in the spring application, it looks like they are using a RestTemplate to deserialise the above into an Event class (which only has an id, imageUrl, name and type as variables). Spring seems to automatically know how to get a POJO from JSON-API.
How can I do the same with Ktor? I tried the simplest:
val response = client.request<List<Event>>("data/event")
With the serialiser as gson:
install(JsonFeature) {
serializer = GsonSerializer()
}
But this results in a list of Event objects with none of their variables correctly set.
I have to make a wrapper class:
data class MyWrapper(val data: List<Event>)
And with that it will populate the list with the objects id set correctly, but none of the other attributes. So by default it looks like Ktor isnt configured for JSON-API. How can I change this?
I believe JSON:API is not supported out of the box. You need to write your own serializer, or use another wrapper class:
class JSONAPI<T>(val type: String, val id: String, val attributes: T)
data class MyWrapper(val data: List<JSONAPI<Event>>)
The problem here is that all fields in Event except of id will be filled. So you will need to adjust deserialization result:
val response: List<Event> = client.request<MyWrapper>("data/event").data.filterNotNull().map { it.attributes.copy(id = it.id) }
Related
Basically I'm trying to integrate with some other service that sends payloads with the following shape
{
"storeId":"123",
"type": "...",
"data": ...,
"createdAt": "...."
}
That shape never changes except for the property data, whose structure depends on the property type.
How can that be achieved with Jackson?
Use JsonNode to convert from string to json nodes JsonNode node = mapper.valueToTree(fromValue);
Then get your node using JsonNode dataNode = node.get("data"), same for type.
Get the class from the type node using Class yourBeanClass = Class.forName("package.+type from step 2");
Tokenize your data node using JsonParser parser = mapper.treeAsTokens(dataNode);
Finally get your bean instance by doing YourBean bean = mapper.readValue(parser, yourBeanClass);
I tried parsing JSON using Kotlin's default serialization library. However, I found it really overwhelming to write a bunch of data classes to deserialize a simple JSON string.
To illustrate,
{
"artists": {
"items": [
{
"genres": [
"desi pop",
"filmi",
"modern bollywood"
],
"images": [
{
"url": "https://i.scdn.co/image/ab6761610000e5ebb2b70762d89a9d76c772b3b6"
}
],
"name": "Arijit Singh",
"type": "artist"
}
]
}
}
for this data, I had to write these many classes,
#Serializable
data class Root(val artists: SubRoot)
#Serializable
data class SubRoot(val items: List<Artist>)
#Serializable
data class Artist(
val genres: List<String>,
val images: List<Image>,
val name: String,
val type: String
)
#Serializable
data class Image(val url: String)
Does anybody know a better way? Some library with in-built magic that does these kind of stuff for me?
If you don't want to use the automatic mapping you can just parse them as JsonElements and do your own thing instead of letting the library map them to those data classes.
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#json-elements
For example, if you want to get that url, you could do:
val root = Json.parseToJsonElement(json)
return root.
jsonObject["artists"]?.
jsonObject?.get("items")?.
jsonArray?.get(0)?.
jsonObject?.get("images")?.
jsonArray?.get(0)?.
jsonObject?.get("url")?.
jsonPrimitive.toString()
)
This specific example will return null if any field couldn't be found while traversing the tree. It will give an IllegalArgumentException if any of the casts fail.
I'm modelling some JSON - and using the following lines
data class Metadata(
val id: String,
val creators: Array<CreatorsModel>
)
along with:
data class CreatorsModel (
val role: String,
val name: String
)
However keep seeing the error: Array property in data class error.
Any ideas why this is?
FYI, the JSON looks like:
{
"id": "123",
"creators": [{
"role": "Author",
"name": "Marie"
}
]
}
In Kotlin you should aim to use List instead of Array where possible. Array has some JVM implications, and although the compiler will let you, the IDE may prompt you to override equals and hashcode manually. Using List will make things much simpler.
You can find out more about the difference here: Difference between List and Array types in Kotlin
I am trying to create Akka Http REST post endpoint mapping the JSON objects to the case class defined
import io.circe.Decoder, io.circe.generic.auto._
case class JobEntity(id: Option[Long] = None, name: String, description: String, json_data :java.sql.blob)
The JSON is of type
{
"id": "124",
"name": "MYJOB",
"description": "Test Job"
}
Now I want to map whole JSON to the 'json_data' as blob defined in the case class
post {
entity(as[JobEntity]) { jobEntity: JobEntity =>
complete(createJob(jobEntity).map(_.asJson))
}
}
I understand .map(_.asJson) would map the json to the JobEntity, Correct me if Its not like this
How do I map the whole JSON to the json_data.
You need to import Circe support methods. See the example . And you need to add dependency de.heikoseeberger % akka-http-circe.
We're currently using Jersey 1.5.1 + Spring for handling JSON requests, and the request structure looks something like this:
{
"id": 34324242,
"foo": "bar",
"info": {
"infofield1": "some value",
"infofield2": "some other value",
"infodetails": {
"details1": "aaaa",
"details2": "bbbb"
}
}
}
The Java class to which this request would map (ideally) looks like this:
#XmlRootElement
public class FooBarRequest {
public Integer id;
public String foo;
public String info;
}
The idea is, that we want to get "info" as plain json string (not parsed to a any java object structure) to store it directly to DB as a BLOB. This doesn't seem to work out-of-a-box for Jersey 1.5.1, we are currently trying to upgrade to Jersey version 1.6, but maybe you have some tips on how to do that?
Maybe there is a ready XmlAdapter, which would perform something like this for us?
If not, does anyone know, how to write one for this particular case?
If you don't want to parse JSON into java, you should simply use a JSON parser directly on the input.