Retrofit or Jackson ObjectMapper maps "aId" property to lowercase "aid" - android

I'm using Jackson 2.9.2 and Retrofit 2.1.0 for some POST operation with a JSONArray as HTML-Header parameter.
The API defines a value which is aId. No matter what I try, my JSON property is always converted to lowercase (aid).
I tested my same code with abId, and it works... Anyone a clue, where my configuration is wrong or which convention(?) is against this property name?
//ObjectMapper initialization
ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
//the data class
import com.fasterxml.jackson.annotation.JsonProperty
data class MyClass(
#JsonProperty
val aId: String? = null, //<-- not working
#JsonProperty
val abId: String? = null //working!
)
//Retrofit call
import retrofit2.http.Body
#POST("log")
fun sendLog(#Body logs: List<MyClass>): Call<MyCall>
//JSON Result in HTML Header
[{
"aid":"some_value", //should be "aId"
"abId":"some_value" //is correct
}]
I tried with following Annotations:
#SerializedName("aId")
#JsonProperty("aId")
#JsonRawValue
#JsonAlias

Try this #get:JsonProperty("aId")

See Michael Ziober' posted link for answer Usage of Jackson #JsonProperty annotation for kotlin data classes
Described issue is a result of Jackson's default bahaviour to not scan private fields. This behaviour can be change with #JsonAutoDetect
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
data class MyClass(
#JsonProperty
val aId: String? = null,
#JsonProperty
val abId: String? = null
)

Related

Variables which names only contain digits (kotlin/android)

I need to create variables that should be named exactly like its API response to save data inside them. So that's why I have to create variables like this:
val 50: String
val 100: String
But as you know I can't do this so how can I create variables which names only contain digits?
If you use Gson library to convert Json api to kotlin Data class, add the same #SerializedName("key_api") annotation.
import com.google.gson.annotations.SerializedName
data class YourResponse {
#SerializedName("50")
val firstVariableName: String? = null,
#SerializedName("100")
val secondVariableName: String? = null
}
With this annotation, Gson will map the key of that Json with your variable name.
You do it by wrapping with `
// define
val `50`: String
val `100`: String
// access
`50` = "123"
`100` = "456"
println("${`50`}${`100`}")
If you use Retrofit with Gson, you should use #SerializedName Check this out

Kotlin Deserialization - JSON Array to multiple different objects

I'm using the 1.0.0 version of kotlin serialization but I'm stuck when I try to deserialize a "flexible" array.
From the Backend API that I don't control I get back an JSON Array that holds different types of objects. How would you deserialize them using kotlin serialization?
Example
This is the API's response
[
{
"id": "test",
"person": "person",
"lastTime": "lastTime",
"expert": "pro"
},
{
"id": "test",
"person": "person",
"period": "period",
"value": 1
}
]
#Serializable
sealed class Base {
#SerialName("id")
abstract val id: String
#SerialName("person")
abstract val person: String
}
#Serializable
data class ObjectA (
#SerialName("id") override val id: String,
#SerialName("title") override val title: String,
#SerialName("lastTime") val lastTime: String,
#SerialName("expert") val expert: String
) : Base()
#Serializable
data class ObjectB (
#SerialName("id") override val id: String,
#SerialName("title") override val title: String,
#SerialName("period") val period: String,
#SerialName("value") val value: Int
) : Base()
Performing the following code result in an error
println(Json.decodeFromString<List<Base>>(json))
error Polymorphic serializer was not found for class discriminator
When you say you don't control the API, is that JSON being generated from your code by the Kotlin serialization library? Or is it something else you want to wrangle into your own types?
By default sealed classes are handled by adding a type field to the JSON, which you have in your objects, but it's a property in your Base class. In the next example it shows you how you can add a #SerialName("owned") annotation to say what type value each class corresponds to, which might help you if you can add the right one to your classes? Although in your JSON example both objects have "type" as their type...
If you can't nudge the API response into the right places, you might have to write a custom serializer (it's the deserialize part you care about) to parse things and identify what each object looks like, and construct the appropriate one.
(I don't know a huge amount about the library or anything, just trying to give you some stuff to look at, see if it helps!)
#cactustictacs solution came very close. He said that "By default sealed classes are handled by adding a type field to the JSON"
But because I didn't had a type property I needed a other field that decides which subclass it should be.
In Kotlin Serializer you can do that by
val format = Json {
classDiscriminator = "PROPERTY_THAT_DEFINES_THE_SUBCLASS"
}
val contentType = MediaType.get("application/json")
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(format.asConverterFactory(contentType))
.build()
where in classDiscriminator you can enter the property that you want. Hope this helps other people in the future.

retrofit generate unformatted complex json object

I am having a very strange problem
I am trying to build a complex json object which contains nested json
I have created it but when I send the request it crash
here is the format that I need
{"JR":"{"Text":"","userID":4,"Context":"","ClinicId":1}","TO":"getWatsonMessage","ver":2}
what I found in the request body as following:
{"JR":"{\"Text\":\"\",\"userID\":4,\"Context\":\"\",\"ClinicId\":1}","TO":"getWatsonMessage","ver":2}
which make the app crash
here is my code
data class MyBody #JvmOverloads constructor(
#SerializedName("ver") val version: Int,
#SerializedName("TO") val methodName: String,
#SerializedName("JR") val jsonParams: String,
#SerializedName("Method") var methodType: String? = null
)
and this is how I create the sub json object
val jsonJR = JSONObject()
jsonJR.put("Text",text)
jsonJR.put("userID",userId)
jsonJR.put("Context",context)
jsonJR.put("ClinicId",clinicId)
can anyone please help ?
It seems like the problem is with your JR of type String.
All the other params are in the proper format right?
So, I suggest that you provide type JR to another custom data class like
data class JRDataClass{
Text : String,
//and the rest of the data types
}

json Enum deserialization breakes kotlin null-safety

I use Kotlin data classes and GSON to deserialize JSON schemas, and implement default values to protect against null-objects in JSON. Also- JSON int enums map to Kotlin enum values using the #SerializedName annotation:
data class Person(#SerializedName("name")
val name: String = ",
#SerializedName("age")
val age: Int = 0,
#SerializedName("hairColor")
val hairColor: Color = Color.NONE)
enum class Color{
#SerializedName("1")
BROWN,
#SerializedName("2")
BLONDE,
NONE
}
Focusing on enum deserialization- this works well for situations when a field matches a known enum or if the field is totally absent from the JSON, in which case the default enum will be implemented.
BUT - if the received enum in JSON doesn't map to a known enum value in my kotlin enum - the resulting deserialized enum will be null!!
{"name":"Joe","age":10,"hairColor":1} ->
Person(name=Joe, age=10, hairColor=BROWN)
{"name":"Jim"} ->
Person(name=Jim, age=0, hairColor=NONE)
{"name":"Jeff", "age":8,"hairColor":3) ->
Person(name=Jane, age=8, hairColor=null)
Gson fools the null safety mechanism of Kotlin by assigning null to a non-null type.
The question - how to map un-known JSON enums to deafult Kotlin enums? My goal is to maintain null-safety with simple implementation.
P.S. - I know I could just parse JSON enums as Ints, and deserialize them later, or use backing fields and custom getters, but I like the elegance and type-safety parsing directly to Kotlin enums.
I wrote a Kotlin wrapper (called it Arson) for Gson that adds missing default values to the deserialized objects. On top of that it also checks for null values that violate the Kotlin non-null safety.
Check it out: https://github.com/taskbase/arson
Use it in your project:
<dependency>
<groupId>com.taskbase.arson</groupId>
<artifactId>arson</artifactId>
<version>1.0</version>
</dependency>
class ArsonTest {
#Test
fun testEnumDeserialization() {
val json = "{'name':'Jim', 'hairColor':'3'}"
// Gson deserializes the value to null
val p1 = Gson().fromJson(json, Person::class.java)
assertNull(p1.hairColor)
// The wrapper replaces null with the default value
val p2 = Arson(gson = Gson()).fromJson(json, Person::class.java)
assertEquals(Color.NONE, p2.hairColor)
}
}
data class Person(
val name: String = "",
val age: Int = 0,
val hairColor: Color = Color.NONE
)
enum class Color {
#SerializedName("1")
BROWN,
#SerializedName("2")
BLONDE,
NONE
}
I was struggling with Gson's lack of Kotlin support as well. Gson is a Java library and does not understand the type system of Kotlin. I tested several other JSON libraries but none worked well enough. I therefore wrote a function that is using the Kotlin reflection library to create a deep copy of an object that adds missing default values.

kotlin bound class reference and generics

I'm switching to Kotlin for Android, but I'm struggling to understand the behavior of generics and Bound Class References.
In java I can serialize an object using Moshi's lib with the following lines:
Moshi moshi = new Moshi.Builder().build();
String string = moshi.adapter(CredentialsResponse.class).toJson(body);
And in Kotlin:
val moshi = Moshi.Builder().build()
var string = moshi.adapter(CredentialsResponse::class.java).toJson(body)
If I want to get the class from an instance, I found two options, but one is not working, and I can't understand why:
This code works:
fun testStack(body: CredentialsResponse) {
val moshi = Moshi.Builder().build()
var string = moshi.adapter(body.javaClass).toJson(body)
}
but this code shows a type mismatch error
fun testStack(body: CredentialsResponse) {
val moshi = Moshi.Builder().build()
var string = moshi.adapter(body::class.java).toJson(body)
}
AFAIK, this call is allowed since 1.1 (https://kotlinlang.org/docs/reference/reflection.html#bound-class-references-since-11), so what am I missing?
There's a subtle difference between the two:
class K
val javaClass: JsonAdapter<K> = moshi.adapter(body.javaClass)
val classJava: JsonAdapter<out K> = moshi.adapter(body::class.java)
Note that body::class.java is marked with out
By calling moshi.adapter(body::class.java).toJson(body) you're try to pass body as in parameter
The difference is, as #AlexeySoshin noted, that the unbound class reference Foo::class is typed with the exact type of the referenced class KClass<Foo>, and the bound one is typed with an out-projection: KClass<out Foo>.
There is a strong reason for this difference. When you reference a class by its name, you can be sure that the class token the reference evaluates to designates exactly the referenced type.
But, when you get a bound class reference for an expression typed as Foo, the expression may evaluate to an instance of Foo's subtype, and the correct type for the type token is KClass<out Foo>, meaning exactly that the actual type argument may be Foo or its subtype.
See this answer for another detailed explanation of the difference between bound and unbound class references: (link)

Categories

Resources