Retrofit/Spring Parsing Data Class to JSON Object - json

I have the following data class:
data class Thing(
val id: Long?,
val title: String,
val description: String,
)
In my Api :
#POST("doThings")
fun createThings(
#Query("thing") thing: Thing
): Call<StatusResponse>
I got the error:
status":500,"error":"Internal Server Error","message":"Unexpected character ('E' (code 69))
In the spring api I made a log output and the data class object arrived as:
"Thing(id=null, title=Something, description=Something more)"
The Retrofit Builder has the GSON Converter but I guess it doesn't work properly:
Retrofit.Builder()
.client(get())
.baseUrl(get<Context>().getString(R.string.base_url))
.addCallAdapterFactory(get<CoroutineCallAdapterFactory>())
.addConverterFactory(get<GsonConverterFactory>())
.build()
Any suggestions? Thanks

You are using the #Query annotation which means your Thing will be serialised as a String and passed as a query parameter in the URL.
You instead want to use the #Body annotation which will serialise the Thing object as JSON and add it to the POST body.
This answer will give you more details on how to use that annotation: https://stackoverflow.com/a/21423093/5577048

Related

Deserializing JSON into Serializable class with generic field - error: Star projections in type arguments are not allowed

Intro
I'm sending JSON messages between two backend servers that use different languages. The producing
server creates a variety of JSON messages, wrapped inside a message with metadata.
The wrapping class is Message, The consuming server has to determine which type of message its
receiving based solely on the message contents.
When I try to use a star-projection to
deserialize the message, I get an error.
Example
import kotlinx.serialization.json.Json
#Language("JSON")
val carJson = """
{
"message_type": "some message",
"data": {
"info_type": "Car",
"name": "Toyota"
}
}
""".trimIndent()
// normally I wouldn't know what the Json message would be - so the type is Message<*>
val actualCarMessage = Json.decodeFromString<Message<*>>(carJson)
Error message
Exception in thread "main" java.lang.IllegalArgumentException: Star projections in type arguments are not allowed, but Message<*>
at kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$SerializersKt__SerializersKt(Serializers.kt:81)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:59)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at ExampleKt.main(example.kt:96)
at ExampleKt.main(example.kt)
Class structure
I want to deserialize JSON into a data class, Message, that has a field with a generic type.
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
#Serializable
data class Message<out DataType : SpecificInformation>(
#SerialName("message_type")
val type: String,
#SerialName("data")
val data: DataType,
)
The field is constrained by a sealed interface, SpecificInformation, with some implementations.
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonClassDiscriminator
#JsonClassDiscriminator("info_type")
sealed interface SpecificInformation {
#SerialName("info_type")
val infoType: String
}
#Serializable
#SerialName("User")
data class UserInformation(
#SerialName("info_type")
override val infoType: String,
val name: String,
) : SpecificInformation
// there are more implementations...
Workaround?
This is a known
issue (kotlinx.serialization/issues/944)
,
so I'm looking for workarounds.
I have control over the JSON structure and libraries - though I have a preference for
kotlinx.serialization.
I can't change that there are two JSON objects, one is inside the other, and the discriminator is
inside the inner-class.
A custom serializer would be great. But I'd prefer to have this configured on the class or file
(with #Serializable(with = ...) or #file:UseSerializers(...)) as using a
custom SerializersModule is not as seamless.
Attempt: JsonContentPolymorphicSerializer
I've written a custom serializer, which only if it's used specifically (which is something I'd like
to avoid). It's also quite clunky, breaks if the data classes change or a new one is added, and
doesn't benefit from the sealed interface.
Can this be improved so that
It can be used generically? Json.decodeFromString<Message<*>>(carJson)
It doesn't have any hard-coded strings?
class MessageCustomSerializer : JsonContentPolymorphicSerializer<Message<*>>(Message::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out Message<*>> {
val discriminator = element
.jsonObject["data"]
?.jsonObject?.get("info_type")
?.jsonPrimitive?.contentOrNull
println("found discriminator $discriminator")
val subclassSerializer = when (discriminator?.lowercase()) {
"user" -> UserInformation.serializer()
"car" -> CarInformation.serializer()
else -> throw IllegalStateException("could not find serializer for $discriminator")
}
println("found subclassSerializer $subclassSerializer")
return Message.serializer(subclassSerializer)
}
}
fun main() {
#Language("JSON")
val carJson = """
{
"message_type": "another message",
"data": {
"info_type": "Car",
"brand": "Toyota"
}
}
""".trimIndent()
val actualCarMessage =
Json.decodeFromString(MessageCustomSerializer(), carJson)
val expectedCarMessage = Message("another message", CarInformation("Car", "Toyota"))
require(actualCarMessage == expectedCarMessage) {
println("car json parsing ❌")
}
println("car json parsing ✅")
}
#Serializable(with = ... - infinite loop
I tried applying MessageCustomSerializer directly to Message...
#Serializable(with = MessageCustomSerializer::class)
data class Message<out T : SpecificInformation>(
//...
But then I couldn't access the plugin-generated serializer, and this causes an infinite loop.
return Message.serializer(subclassSerializer) // calls 'MessageCustomSerializer', causes infinite loop
#Serializer(forClass = ...) - not generic
In addition to annotating Message with #Serializable(with = MessageCustomSerializer::class), I
tried
deriving a plugin-generated serializer:
#Serializer(forClass = Message::class)
object MessagePluginGeneratedSerializer : KSerializer<Message<*>>
But this serializer is not generic, and causes an error
java.lang.AssertionError: No such value argument slot in IrConstructorCallImpl: 0 (total=0).
Symbol: MessageCustomSerializer.<init>|-5645683436151566731[0]
at org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpressionKt.throwNoSuchArgumentSlotException(IrMemberAccessExpression.kt:66)
at org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression.putValueArgument(IrFunctionAccessExpression.kt:31)
at org.jetbrains.kotlinx.serialization.compiler.backend.ir.IrBuilderExtension$DefaultImpls.irInvoke(GeneratorHelpers.kt:210)
at org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializableCompanionIrGenerator.irInvoke(SerializableCompanionIrGenerator.kt:35)
You are asking many things here, so I will simply try to give some pointers in regards to the errors you are making which you seem to be stuck on. With those in mind, and reading the documentation I link to, I believe you should be able to resolve the rest yourself.
Polymorphic serialization
Acquaint yourself with kotlinx.serialization polymorphic serialization. When you are trying to serialize Message<*> and DataType you are trying to use polymorphic serialization.
In case you are serializing Message<*> as the root object, specifying PolymorphicSerializer explicitly (as I also posted in the bug report you link to) should work. E.g., Json.decodeFromString( PolymorphicSerializer( Message::class ), carJson ).
P.s. I'm not 100% certain what you are trying to do here is the same as in the bug report. Either way, specifying the serializer explicitely should work, whether or not it is a bug that you shouldn't be required to do so.
The message_type and info_type fields you have in Message and DataType respectively are class discriminators. You need to configure this in your Json settings, and set the correct SerialName on your concrete classes for them to work. Using a different class discriminator per hierarchy is only possible starting from kotlinx.serialization 1.3.0 using #JsonClassDiscriminator.
Overriding plugin-generated serializer
But then I couldn't access the plugin-generated serializer, and this causes an infinite loop.
#Serializable(with = ...) overrides the plugin-generated serializer. If you want to retain the plugin-generated serializer, do not apply with.
When you are serializing the object directly (as the root object), you can still pass a different serializer to use as the first parameter to encode/decode. When you want to override the serializer to use for a specific property nested somewhere in the root object, use #Serializable on the property.
Polymorphism and generic classes
The "No such value argument slot in IrConstructorCallImpl: 0" error is to be expected.
You need to do more work in case you want to specify a serializer for polymorphic generic classes.

Include class name (Kotlin data class) in JSON response in Quarkus

I'm new to Quarkus and Kotlin and truth be told, I'm not quite sure yet what goes on behind the scenes and which JSON library is actually responsible for rendering the JSON response from a resource when I set the #Produces(MediaType.APPLICATION_JSON) on my function. But I'm returning an instance of a data class that I created from that method and all of the fields in that data class are rendered in the response. However, I have multiple response classes and I would like to include the name of the class in the JSON response. What I have now is a String field that is simply hard coded to the name of the class but that is ugly as I have to repeat the class name:
data class StuffInitiatedResponse (
val id: String,
val projectId: String
) {
val operation = "StuffInitiatedResponse"
}
data class StuffCompletedResponse (
val id: String,
val projectId: String,
) {
val operation = "StuffCompletedResponse"
}
And in my service class:
#Path("/myservice")
class MyService {
#POST
#Path("{project}/{path:.*}")
#Produces(MediaType.APPLICATION_JSON)
fun initiateStuff(#PathParam project: String,
#PathParam path: String,
#QueryParam("completedId") completedId: String?) : StuffInitiatedResponse {
if (completedId == null) {
println("I've initiated stuff")
return StuffInitiatedResponse(UUID.randomUUID().toString(), project)
} else {
println("I've completed stuff")
return StuffCompletedResponse(completedId, project)
}
}
}
This produces what I expect but as I said, I'm annoyed that I have to repeat the class name in the "response" field of the data classes. Is there some way for me to have the class name embedded in the JSON?
The JSON library depends on the dependencies you defined. It can be either Jackson or Yasson.
I recommend using Jackson and, in this case, you can use the #JsonTypeInfo annotation on your serialized classes, which has some options to include the type in the JSON output.

Saving Document directly as a value of id in Spring-data-Couchbase

I want to save a JSON object as a Document in Couchbase. The id of this document is supposed to be retrieved from this JSON object and the value is supposed to be this JSON object itself. Since this JSON is too complex, I haven't mapped it directly to any POJO class, but I have created a Simple POJO, which has two fields as shown below
#Document
public class SimplePojo{
#Id
private String id;
#Field()
private String complexJsonString;//the JSON string is stored in this variable
}
I also have a SimplePojoRepository as shown below
#Component
public interface SimplePojoRepository extends CouchbaseRepository<SimplePojo, String>{
}
Now, I am setting the id and complexJsonString manually before calling the save method:-
SimplePojo myObj= new SimplePojo();
myObj.setId(myKey);
myObj.setComplexJsonString(jsonString);
simplePojoRepository.save(myObj);
This is working fine, but it is saving the Document in below format
myKey: {
complexJsonString : {//the original json Object here}
}
but I don't want this, I want to save it like this:-
myKey : {//the original json Object here}
So, to make it clear, I don't want to save my JSON object as a value of complexJsonString but rather, directly as a value of the myKey . Can someone please guide me on how to achieve this?
If you want to store the complexJsonString as a nested entity within your main object, you have to transform it in a Pojo:
myObj.setSomeEntity(new SomeEntity())
You can easily transform your JSON-encoded String to object using jackson's ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.readValue( jsonString, SomeEntity.class);
However, if you don't have control on the structure of this json, you will need to use the standard Java SDK instead of the Spring Data One:
JsonObject obj = JsonObject.create().put(this.documentTypeName, this.documentValue)
.put("attrNam1", "attrValue1")
.put("attrNam2", "attrValue2")
JsonDocument doc = JsonDocument.create(session.getId(), maxExpirationTime, obj);
bucket.upsert(doc)
In the case above, you will need to parse your JSON-encoded string using some lib (ex: gson/jackson) and then convert it to a couchbase JsonDocument.
Lastly, you could also leave your code as it is and use the N1QL function DECODE_JSON() whenever you need to access some property of this json string.
ex:
SELECT
i.itemName as itemName,
SUM(i.quantity) AS totalQuantity
FROM sessionstore s
UNNEST DECODE_JSON(s.sessionCart).shoppingCart.items i
WHERE s.sessionCart IS NOT MISSING
GROUP BY i.itemName
ORDER BY SUM(i.quantity) DESC
LIMIT 10

Fetch the json from HttpEntity

I have an object of akka.http.scaladsl.model.HttpEntity looking like HttpEntity("application/json", {Myjson here})
Is there a way i can fetch my json from the entity without any string manipulations(other than converting to a string and doing a split)
You will need a JSON parser and a glue code between the JSON parser and Akka Http called Unmarshaller.
Akka Http includes unmarshallers for spray-json library. You can find more unmarshallers in hseeberger/akka-http-json library.
If you choose to use spray-json a pseudo code for that would be
case class MyJson(v1: String, v2: Int)
object JsonProtocol extends DefaultJsonProtocol {
implicit val myFormat = jsonFormat2(MyJson)
}
val resp: Future[MyJson] = Unmarshal(response).to[MyJson]

ObjectId is not serialized to JSON

I am using scalatra and configured my servlet to always return JSON (as described in the respective guide). Using the MongoDB and Salat leads me to the point where I read a MongoDBObject back into my case class - which seems to work great.
My case class:
import org.bson.types.ObjectId
import com.novus.salat.annotations.raw.Key
case class Player(_id: ObjectId, firstName: String, ...)
Printing the case class object outputs this:
Player(547489ee93f4272e548ded63,Peter,...)
As you can see, the objectid is a org.bson.types.ObjectId.
The automatical serialization to JSON sends this to the browser:
{"_id":{},"firstName":"Peter",...}
Where is my ObjectID? What am I doing wrong?
I found the following on the web:
https://gist.github.com/dozed/5631680
After a small test it seems as if all I had to do was changing the code in my servlet from
protected implicit val jsonFormats: Formats = DefaultFormats
to
protected implicit val jsonFormats: Formats = DefaultFormats + new ObjectIdSerializer
and add
import org.json4s.mongo.ObjectIdSerializer
Maybe this will help another Scalatra-NOOB... ;-)