Deserialize Property with pattern using Jackson - kotlin

There's a request that i'm using and it's parameter name is __parameter10/02/2020, the date is sent into request.
Is there a way to deserialize this using the Property annotation? Also how can i do this with the custom serializer?
Json Sample:
{"yearly_return10/02/2020" : "2.87", "__monthlyreturn_current10/02/2020": "-0.0853703899653",}

You may try #JsonAnySetter
class Response {
lateinit var yearlyReturn: BigDecimal
lateinit var monthlyReturnCurrent: BigDecimal
#JsonAnySetter
fun set(key: String, value: String) {
when {
key.contains("yearly_return") -> yearlyReturn = BigDecimal(value)
key.contains("monthlyreturn_current") -> monthlyReturnCurrent = BigDecimal(value)
}
}
}
val objectMapper: ObjectMapper = ObjectMapper().registerModules(
ParameterNamesModule(),
Jdk8Module(),
JavaTimeModule(),
KotlinModule()
)
val response = objectMapper.readValue<Response>("""{"yearly_return10/02/2020" : "2.87", "__monthlyreturn_current10/02/2020": "-0.0853703899653"}""")

Related

How to use the data type java.util.UUID in Moshi?

I used the data type java.util.UUID in my data models and I have used Moshi for serialization.
But I encountered an error saying that "Platform class java.util.UUID requires explicit JsonAdapter to be registered"
I have gone through the documentation of Moshi for writing custom adapters and I tried to replicate it accordingly.
I wrote an adapter and added it to a moshi instance. But still I encounter the same error .
Adapter
class UUIDAdapter {
#ToJson
fun toJson(value:java.util.UUID):java.util.UUID{
return value
}
#FromJson
fun fromJson(value: String):java.util.UUID{
return java.util.UUID.fromString(value)
}
}
Model
#JsonClass(generateAdapter = true)
data class AddWorkspace(
#Json(name = "user_id")
val user_id: UUID,
#Json(name = "name")
val name:String,
#Json(name = "descp")
val descp:String,
#Json(name = "created_at")
val created_at:String
)
Moshi
private val moshi = Moshi.Builder()
.add(UUIDAdapter())
.build()
private val retrofitBuilder = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(MoshiConverterFactory.create(moshi))
What else am I missing so that I can use the adapter correctly ?
Edit : Well, the methods toJson and fromJson are not being called in the first place. I tried to implement the JsonAdapter class and override the methods toJson and fromJson, but the issue I face here is that in case of the method toJson, I need to send a java.util.UUID value, but the JsonWriter cannot write a value of such data type.
Please suggest me a way to work my way through this. Thanks :)
UUID adapter
class UUIDAdapter:JsonAdapter<UUID>(){
#FromJson
override fun fromJson(reader: JsonReader): UUID? {
return UUID.fromString(reader.readJsonValue().toString())
}
#ToJson
override fun toJson(writer: JsonWriter, value: UUID?) {
writer.jsonValue(value)
}
}
You're so close. Change the #ToJson to this:
#ToJson
fun toJson(value:java.util.UUID): String {
return value.toString()
}
as Jesse described just use:
class UuidAdapter {
#FromJson
fun fromJson(uuid: String): UUID = UUID.fromString(uuid)
#ToJson
fun toJson(value: UUID): String = value.toString()
}

Jackson Databind not setting default value

In my quarkus application I have an endpoint that takes in a DTO, with a field that has a default value. When I don't send that field, I still get the exception
com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of
`FooDTO`, problem: Parameter specified as non-null is null: method
io.otherstuff.FooDTO.<init>, parameter someListVariable
at [Source: (io.quarkus.vertx.http.runtime.VertxInputStream); line: 4, column: 1]
The class looks like this:
class FooDTO(
override var someStringVar: String,
override var someListVariable: List<Int> = emptyList(),
): BarDTO
---------------------------------------------
interface BarDTO {
var someStringVar: String
var someListVar: List<Int>
}
Now if I send a payload like this
{
"someStringVar": "Hello Stackoverflow",
"someListVar": []
}
it is working perfectly fine, but when I drop "someListVar" I get the exception from above, even though it should just initialize it as an empty list.
Any help is much appreciated!
The problem is, that during desalinization, the library (fasterxml) calls the primary constructor with null: FooDTO("Hello Stackoverflow", null). The call ends up with the exception as the someListVariable parameter is not nullable (default value is used only when the paremeter is not provided at all, not when it's null).
One option of solving the problem would be providing an explicit JsonCreator:
class FooDTO(
override var someStringVar: String,
override var someListVariable: List<Int> = emptyList()) : BarDTO {
companion object {
#JvmStatic
#JsonCreator
fun of(
#JsonProperty("someStringVar") someStringVar: String,
#JsonProperty("someListVariable") someListVariable: List<Int>?) =
FooDTO(someStringVar, someListVariable ?: emptyList())
}
}
Another posibility is using secondary constructor instead of the default value:
class FooDTO : BarDTO {
override var someStringVar: String
override var someListVariable: List<Int>
#JsonCreator
constructor(
#JsonProperty("someStringVar") someStringVar: String,
#JsonProperty("someListVariable") someListVariable: List<Int>?) {
this.someStringVar = someStringVar
this.someListVariable = someListVariable ?: emptyList()
}
}
Both options are unfortunately a bit verbose.

Use Kotlin's data class in service-proxy of Vert.x

I'm trying to pass data class to the service-proxy of Vert.x like this:
data class Entity(val field: String)
#ProxyGen
#VertxGen
public interface DatabaseService {
DatabaseService createEntity(Entity entity, Handler<AsyncResult<Void>> resultHandler);
}
However, the service-proxy requires a DataObject as the parameter type.
Below are what I've tried so far.
First, I rewrite the data class as:
#DataObject
data class Entity(val field: String) {
constructor(json: JsonObject) : this(
json.getString("field")
)
fun toJson(): JsonObject = JsonObject.mapFrom(this)
}
Although this works, the code is redundant, so I tried the kapt with the following generator:
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
roundEnv.getElementsAnnotatedWith(ProxyDataObject::class.java).forEach { el ->
val className = el.simpleName.toString()
val pack = processingEnv.elementUtils.getPackageOf(el).toString()
val filename = "Proxy$className"
val classBuilder = TypeSpec.classBuilder(filename)
val primaryConstructorBuilder = FunSpec.constructorBuilder()
val secondaryConstructorBuilder = FunSpec.constructorBuilder().addParameter("json", JsonObject::class)
val secondaryConstructorCodeBlocks = mutableListOf<CodeBlock>()
el.enclosedElements.forEach {
if (it.kind == ElementKind.FIELD) {
val name = it.simpleName.toString()
val kClass = getClass(it) // get the corresponding Kotlin class
val jsonTypeName = getJsonTypeName(it) // get the corresponding type name in methods of JsonObject
classBuilder.addProperty(PropertySpec.builder(name, kClass).initializer(name).build())
primaryConstructorBuilder.addParameter(name, kClass)
secondaryConstructorCodeBlocks.add(CodeBlock.of("json.get$jsonTypeName(\"$name\")"))
}
}
secondaryConstructorBuilder.callThisConstructor(secondaryConstructorCodeBlocks)
classBuilder
.addAnnotation(DataObject::class)
.addModifiers(KModifier.DATA)
.primaryConstructor(primaryConstructorBuilder.build())
.addFunction(secondaryConstructorBuilder.build())
.addFunction(
FunSpec.builder("toJson").returns(JsonObject::class).addStatement("return JsonObject.mapFrom(this)").build()
)
val generatedFile = FileSpec.builder(pack, filename).addType(classBuilder.build()).build()
generatedFile.writeTo(processingEnv.filer)
}
return true
}
Then I can get the correct generated file by simply writing the original data class, but when I execute the building after cleaning, I still get the following error:
Could not generate model for DatabaseService#createEntity(ProxyEntity,io.vertx.core.Handler<io.vertx.core.AsyncResult<java.lang.Void>>): type ProxyEntity is not legal for use for a parameter in proxy
It seems that the generated annotation #DataObject is not processed.
So what should I do? Is there a better solution?

Custom serializer with polymorphic kotlinx serialization

With kotlinx.serialization polymorphism, I want to get
{"type":"veh_t","owner":"Ivan","bodyType":"cistern","carryingCapacityInTons":5,"detachable":false}
but I get
{"type":"kotlin.collections.LinkedHashMap","owner":"Ivan","bodyType":"cistern","carryingCapacityInTons":5,"detachable":false}
I use the following models
interface Vehicle {
val owner: String
}
#Serializable
#SerialName("veh_p")
data class PassengerCar(
override val owner: String,
val numberOfSeats: Int
) : Vehicle
#Serializable
#SerialName("veh_t")
data class Truck(
override val owner: String,
val body: Body
) : Vehicle {
#Serializable
data class Body(
val bodyType: String,
val carryingCapacityInTons: Int,
val detachable: Boolean
//a lot of other fields
)
}
I apply the following Json
inline val VehicleJson: Json get() = Json(context = SerializersModule {
polymorphic(Vehicle::class) {
PassengerCar::class with PassengerCar.serializer()
Truck::class with TruckKSerializer
}
})
I use serializer TruckKSerializer because the server adopts a flat structure. At the same time, in the application I want to use an object Truck.Body. For flatten I override fun serialize(encoder: Encoder, obj : T) and fun deserialize(decoder: Decoder): T in Serializator using JsonOutput and JsonInput according to the documentation in these classes.
object TruckKSerializer : KSerializer<Truck> {
override val descriptor: SerialDescriptor = SerialClassDescImpl("Truck")
override fun serialize(encoder: Encoder, obj: Truck) {
val output = encoder as? JsonOutput ?: throw SerializationException("This class can be saved only by Json")
output.encodeJson(json {
obj::owner.name to obj.owner
encoder.json.toJson(Truck.Body.serializer(), obj.body)
.jsonObject.content
.forEach { (name, value) ->
name to value
}
})
}
#ImplicitReflectionSerializer
override fun deserialize(decoder: Decoder): Truck {
val input = decoder as? JsonInput
?: throw SerializationException("This class can be loaded only by Json")
val tree = input.decodeJson() as? JsonObject
?: throw SerializationException("Expected JsonObject")
return Truck(
tree.getPrimitive("owner").content,
VehicleJson.fromJson<Truck.Body>(tree)
)
}
}
And finally, I use stringify(serializer: SerializationStrategy<T>, obj: T)
VehicleJson.stringify(
PolymorphicSerializer(Vehicle::class),
Truck(
owner = "Ivan",
body = Truck.Body(
bodyType = "cistern",
carryingCapacityInTons = 5,
detachable = false
)
)
)
I end up with {"type":"kotlin.collections.LinkedHashMap", ...}, but I need {"type":"veh_t", ...}
How do I get the right type? I want using polymorphism for Vehicle and encode Body object with Truck.Body.serializer() to flatten.
With this serialization, the PassengerCar class runs fine.
VehicleJson.stringify(
PolymorphicSerializer(Vehicle::class),
PassengerCar(
owner = "Oleg",
numberOfSeats = 4
)
)
Result is correct:
{"type":"veh_p","owner":"Oleg","numberOfSeats":4}
I think the problem is the custom serializer TruckKSerializer.
And I noticed if I use in my overridden fun serialize(encoder: Encoder, obj : T) next code
encoder
.beginStructure(descriptor)
.apply {
//...
}
.endStructure(descriptor)
I get the correct type but cannot flatten the object Truck.Body using its serializer.
the correct way to open and close a composite {}
is this code
val composite = encoder.beginStructure(descriptor)
// use composite instead of encoder here
composite.endStructure(descriptor)
and you should be able to serialize Body using .encodeSerializable(Body.serializer(), body)
and always pass the descriptor along otherwise it will fall back to stuff like that LinkedhashMap for the json dictionary

RetroFit, SimpleXMLConverter

I am using Retrofit with simpleXMLConverter to get data out of an xml and use it in my android application. However, I am unable to bind the value of a certain tag on my value in my class. In my situation, I'm trying to bind the mtc:gas_type to my price variable in the Gasoline Class. I did manage to bind a value from a simple XML I found online with just 2 tags so it means I'm doing something wrong with this particular XML...
Error(Which means it can't find the tag):
java.lang.RuntimeException: org.simpleframework.xml.core.ValueRequiredException: Unable to satisfy #org.simpleframework.xml.Element(data=false, name=mtc:gas_type, required=true, type=void) on field 'price' private double com.example.alexander.ridy.Model.domain.Gasoline.price for class com.example.alexander.ridy.Model.domain.Gasoline at line -1
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:44)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:223)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:186)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:92)
at com.example.alexander.ridy.View.fragments.Ride.VehicleDetails$determineGasolineType$1.run(VehicleDetails.kt:101)
at java.lang.Thread.run(Thread.java:764)
Caused by: org.simpleframework.xml.core.ValueRequiredException: Unable to satisfy #org.simpleframework.xml.Element(data=false, name=mtc:gas_type, required=true, type=void) on field 'price' private double com.example.alexander.ridy.Model.domain.Gasoline.price for class com.example.alexander.ridy.Model.domain.Gasoline at line -1
at org.simpleframework.xml.core.Composite.validate(Composite.java:644)
at org.simpleframework.xml.core.Composite.readElements(Composite.java:449)
at org.simpleframework.xml.core.Composite.access$400(Composite.java:59)
at org.simpleframework.xml.core.Composite$Builder.read(Composite.java:1383)
at org.simpleframework.xml.core.Composite.read(Composite.java:201)
at org.simpleframework.xml.core.Composite.read(Composite.java:148)
at org.simpleframework.xml.core.Traverser.read(Traverser.java:92)
at org.simpleframework.xml.core.Persister.read(Persister.java:625)
at org.simpleframework.xml.core.Persister.read(Persister.java:606)
at org.simpleframework.xml.core.Persister.read(Persister.java:584)
at org.simpleframework.xml.core.Persister.read(Persister.java:562)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:36)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23) 
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:223) 
at retrofit2.OkHttpCall.execute(OkHttpCall.java:186) 
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:92) 
at com.example.alexander.ridy.View.fragments.Ride.VehicleDetails$determineGasolineType$1.run(VehicleDetails.kt:101) 
at java.lang.Thread.run(Thread.java:764) 
XML:
https://www.globalpetrolprices.com/api/getGasXML_weekly.php?gasoline_diesel=2&rate=EUR&countries=183&p=c5ab37e86d514beadb60d3d3d682f363
Endpoint interface:
interface Endpoint {
#GET("/api/getGasXML_weekly.php?gasoline_diesel=2&rate=LC&countries=183&p=c5ab37e86d514beadb60d3d3d682f363")
fun getDiesel(): Call<Gasoline>
#GET("/api/getGasXML_weekly.php?gasoline_diesel=1&rate=LC&countries=183&p=c5ab37e86d514beadb60d3d3d682f363")
fun getSuper95(): Call<Gasoline>
#GET("/api/getGasXML_weekly.php?gasoline_diesel=3&rate=LC&countries=183&p=c5ab37e86d514beadb60d3d3d682f363")
fun getLPG(): Call<Gasoline>
RetrofitClientInstance:
class RetrofitClientInstance {
private var retrofit: Retrofit? = null
private val BASE_URL = "https://www.globalpetrolprices.com"
fun getRetrofitInstance(): Retrofit? {
retrofit = retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(create())
.build()
return retrofit
}
Gasoline Class:
#Root(strict = false) class Gasoline {
#field:Element(name="mtc:gas_type")
var price : Double = 0.0
lateinit var gasolineType : GasolineType
constructor()
The call:
fun determineGasolineType() {
val mySpinner = view?.findViewById(R.id.gasolineSpinner) as Spinner
val text = mySpinner.selectedItem.toString()
val service = RetrofitClientInstance().getRetrofitInstance()!!.create(Endpoint::class.java!!)
lateinit var call : Call<Gasoline>
lateinit var resp : Response<Gasoline>
Thread({
resp = service.getDiesel().execute()
Log.d("",resp.body().toString())
}).start()