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.
Related
I am getting a response something like this where status, message & data keys will remain the same across the different API responses & only the content inside the data JSON object will change, in this example, I have a JSON object with a member key in other response I can have tournament key.
{
"status": true,
"message": "Success",
"data": {
"member": {
"id": 21,
"emailAddress": "abc#xyz.com",
"firstName": "ABC"
}
}
}
Currently, I am making use of generics and doing something like this
data class SignInResponse(
val `data`: Data<Member>
) : BaseResponse()
and BaseResponse class has common JSON keys that I am getting, Here I'm using generics in Data class passing the JSON class that is changing.
open class BaseResponse {
val status: Boolean = false
val message: String = UNDEFINED
}
#Keep
data class Data<T>(val actualData: T)
But this approach is incomplete because the above code will expect a JSON key actualData but here JSON key can be a member , tournament , or anything. How can I pass the class in Data class so that it can support the above JSON response?
Is this as a result from a network call? I've never used generics in trying to parse incoming network calls before.
I'd have a baseData class that contains any common fields between member or tournament - like id or name and then subclass that with a concrete implementation for each possibility which would hold the unique data.
then your BaseResponse class could just be
data class BaseResponse(
val status: Boolean
val message: String
val data: BaseData
)
One way to represent this would be via a class hierarchy rather than generics, because this extra wrapping is not present in the JSON, nor in your conceptual data structure.
A sealed class is what I would usually go for in this case.
However, it all depends on which JSON serialization library you're using. If you control both ends of the communication, I would definitely go for Kotlinx Serialization, which supports sealed hierarchies (and polymorphism in general) in a nice way.
If you don't control the serialization side, maybe a temporary union of all fields as #Chris mentioned is your simplest option.
I ended up using the following approach, where I create a generic for data class
data class BaseResponse<T>(
val status: Boolean, val message: String, val data: T
)
Above implementation enables me to pass model of changing JSON Object inside the data field
data class SignInResponse(
val token: String, val member: Member
)
and after summing up everything we can use it like this
interface BasicxBookingServices {
#POST(Urls.SIGNIN_URL)
suspend fun signIn(#Body body: SignInRequest): Response<BaseResponse<SignInResponse>>
}
I'm new to coding in kotlin and want to implement an immutable class that represents a project with various fields inside.
The easiest way to do this is by using a data class and using the copy() method so that anytime one of the app user modifies a field it results in the backend in a call to the copy method with the modified field producing the new project.
My problem is that this way does not allow for prior checking of parameters (eg : limit string size of the owner, making sure the number of people added to the project is reasonable etc).
If this was java, I'd use a builder pattern but this seems to defeat the purpose of kotlin, and i've read articles that are positive to using builders in kotlin (https://www.baeldung.com/kotlin/builder-pattern)
and others that are completely against (https://code-held.com/2021/01/23/dont-use-builder-in-kotlin/).
I haven't found any way to "modify" the copy method and to add the parameter sanitization checks that are needed for each parameter. I would appreciate any "smooth" idea to implement this, if anybody has found it. The goal would also be to throw exeptions/sealed classes variables so that the app UI can tell the user what went wrong instead of a generic error message just mentioning that the project was not modified.
I agree with the second link. If you look at the comments on the Baeldung article, you'll see even they were convinced and pledged to revise the article.
You can throw exceptions in an init block but if these are exceptions that are not caused by programmer error, it would be more Kotlin-idiomatic to expose a single constructor-like function that returns a wrapper or just null for invalid input.
Examples:
data class Person(val name: String, val age: Int = 0) {
init {
if (age < 0) {
throw IllegalArgumentException("Age $age is less than 0.")
}
}
}
If you want to return a wrapper or nullable, a data class isn't suitable for preventing invalid input because the generated copy() function will always return a fully constructed object. Sadly, Kotlin does not support overriding the generated copy() function.
sealed class Result<T>
data class Success<T>(val value: T): Result<T>()
data class Failure<T>(val reason: String): Result<T>()
class Person private constructor(val name: String, val age: Int = 0) {
companion object {
fun build(name: String, age: Int = 0): Result<Person> {
return when {
age < 0 -> Failure("Age $age is less than 0.")
else -> Success(Person(name, age))
}
}
}
fun buildCopy(name: String = this.name, age: Int = this.age) = build(name, age)
}
I am working on android application and want to manage null value that is comes from API.
I did not found the best solution.
I also try these methods
1. While generating getter in model class i give a conditional statement in getter like
if(value == null){
value = ""
}
but this is not a right way because of i have to write this code for each and every getter in whole application.
2. replace the string from 'null' to "" before JSON parsing.
this is replace all type of value (int/float etc) in to blank string that is invalid.
You can use JsonDeserializer to create your Gson.
You can create model class like this
class ExampleModel {
#SerializedName("id")
#Expose
val id: Int? = null
#SerializedName("category")
#Expose
val category: String? = null
}
This class will be able to handle null value and data as well.
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
)
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)