Calculated transient value is null after deserialisation with Gson - kotlin

I run into an error which I do not understand after deserializing with gson.
My usecase is way more complex, but I created this test below which shows the behaviour I see.
The issue is the calculated transient value (allObjs) that is null after deserialisation. The unit test fails on the last assert.
Apparently the #Transient prevents correct initialisation of allObjs after deserialisation.
Is this expected behaviour? Can I do anything to make the field work in this way?
If not then I have to convert all the transient fields to functions like getThemAll(), which does work.
I hope for any insights.
Regards, Rob
class CoupleDeserializeTest {
data class Couple( val objA: String, val objB: String){
#Transient
val allObjs: List<String> = listOf(objA, objB)
fun getThemAll() = listOf(objA, objB)
}
#Test
fun testDe_SerializeCouple() {
val couple = Couple("my", "text")
// This succeeds
Assert.assertNotNull(couple.allObjs)
val gson = Gson()
val json = gson.toJson(couple)
// This succeeds
Assert.assertEquals("{\"objA\":\"my\",\"objB\":\"text\"}", json)
val coupleDeserialized = gson.fromJson<Couple>(json, Couple::class.java)
// This succeeds
Assert.assertNotNull(coupleDeserialized.getThemAll())
// This fails
Assert.assertNotNull(coupleDeserialized.allObjs)
}
}

If you are marking a field as Transient, it will ignore the field from serialisation or deserialisation, snippet from JvmFlagAnnotations
/**
* Marks the JVM backing field of the annotated property as `transient`, meaning that it is not
* part of the default serialized form of the object.
*/
#Target(FIELD)
#Retention(AnnotationRetention.SOURCE)
#MustBeDocumented
public actual annotation class Transient
EDIT
It will ignore it in initialisation because, the value of objA and objB will be initialised using serialisation meaning objA and objB have not assigned by the time you allObjs gets initialised, to get the values later instead of directly assigning, you can use get()
data class Couple( val objA: String, val objB: String){
#Transient
val allObjs: List<String>
get() = listOf(objA, objB)
fun getThemAll() = listOf(objA, objB)
}

Related

Why #JsonIgnore annotation doesn't work during deserializing data?

I have a data https://gist.githubusercontent.com/iva-nova-e-katerina/fc1067e971c71a73a0b525a21b336694/raw/954477261bb5ac2f52cee07a8bc45a2a27de1a8c/data2.json a List with seven CheckResultItem elements.
I trying to parse them this way:
import com.fasterxml.jackson.module.kotlin.readValue
...
val res = restHelper.objectMapper.readValue<List<CheckResultItem>>(text)
which gives me the following error:
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.fmetric.validation.api.Brick] value failed for JSON property upperLevelBricks due to missing (therefore NULL) value for creator parameter upperLevelBricks which is a non-nullable type
at [Source: (StringReader); line: 1, column: 714] (through reference chain: java.util.ArrayList[0]->com.fmetric.validation.api.checking.CheckResultItem["brick"]->com.fmetric.validation.api.Brick["upperLevelBricks"])
at com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:116)
There is #JsonIgnore annotation in data class :
data class Brick(
val id: UUID?,
val name: String,
val type: BrickType,
val propertyValues: List<ProjectBrickPropertyValue<*>>,
#JsonIgnore
val upperLevelBricks: ArrayList<Brick>,
val downLevelBricks: ArrayList<Brick>,
var drawingDetails: List<BrickDrawingDetails>?
) {
But it seems it doesn't work. Could you explain me what is wrong?
UPD: Also I have tried #JsonIgnoreProperties({"upperLevelBricks"}) class annotation but it doesn't work. My solution was to set a default value
val upperLevelBricks: ArrayList<Brick> = arrayListOf(),
But I think that annotations should work!
Actually, it works, but not the way you think. During deserialization #JsonIgnore ignores the respectful field in JSON, like it wasn't there (but it's doesn't make sense in this case, because it's initially absent in JSON).
In Java, Jackson would've just instantiated class with null value for the absent field (because all object types in Java are nullable, which means they allow the value to be set to null). But in Kotlin, a property should be explicitly marked as nullable (val upperLevelBricks: List<Brick>?) or have a default value (val upperLevelBricks: List<Brick> = emptyList()) so that Jackson could create a class instance in this case.
Note that approach with default value for property won't work (unless you additionally mark it with #JsonIgnore) if this field is present in JSON but explicitly set to null:
{
...
"upperLevelBricks": null,
...
}
Anyway, if you don't want to change the API of your Brick class you may provide a default value for this field only when it's created during Jackson deserialization (and only if it's absent/null in JSON) via custom deserializer:
object EmptyListAsDefault : JsonDeserializer<List<Brick>>() {
override fun deserialize(jsonParser: JsonParser, context: DeserializationContext): List<Brick> =
jsonParser.codec.readValue(
jsonParser,
context.typeFactory.constructCollectionType(List::class.java, Brick::class.java)
)
override fun getNullValue(context: DeserializationContext): List<Brick> = emptyList()
}
data class Brick(
//...
#JsonDeserialize(using = EmptyListAsDefault::class)
val upperLevelBricks: List<Brick>,
//...
)

How does Jackson marshalling work with Kotlin data class?

I have a simple data class being returned from a REST endpoint.
data class SummarizedReturn(
val NET_CASH_FLOW: BigDecimal,
val ROI_PERCENTAGE: BigDecimal
)
When it is returned, the object looks like this:
{
summarizedReturn: {
net_CASH_FLOW: -194703.12028723184,
roi_PERCENTAGE: -35,
}
}
This is not what I need. I need all letters to be capitalized. So I added the JsonProperty annotation
data class SummarizedReturn(
#JsonProperty("NET_CASH_FLOW")
val NET_CASH_FLOW: BigDecimal,
#JsonProperty("ROI_PERCENTAGE")
val ROI_PERCENTAGE: BigDecimal,
)
This did not change anything. I still get the result the same as above.
I then changed the property names and kept the annotation
data class SummarizedReturn(
#JsonProperty("NET_CASH_FLOW")
val netCashFlow: BigDecimal,
#JsonProperty("ROI_PERCENTAGE")
val roiPercentage: BigDecimal,
)
and that returned what I wanted.
{
summarizedReturn: {
NET_CASH_FLOW: -194703.12028723184,
ROI_PERCENTAGE: -35,
}
}
Why did the annotation not work on the initial version of the class? How can I keep my property names all capitalized and have the Jackson value to be the same?
There is an issue with interoperability of Java annotations in Kotlin code. You can register Jackson's Kotlin module to get rid of this problems:
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
fun main() {
val mapper = jacksonObjectMapper() // <= shortcut to ObjectMapper().registerKotlinModule()
println(mapper.writeValueAsString(SummarizedReturn(
BigDecimal("-194703.12028723184"),
BigDecimal("-35"))))
}
Output:
{"NET_CASH_FLOW":-194703.12028723184,"ROI_PERCENTAGE":-35}
This will also require you to add com.fasterxml.jackson.module:jackson-module-kotlin to your dependencies.
PS: Alternatively you can solve it by using slightly different target:
#JsonPropery => #get:JsonPropery (or field:#JsonPropery in case this data class will also be used for deserialization)

Android ROOM, insert won't return ID with first insert, but will return with 2nd insert onwards

School project and I'm pretty new to Android development.
The problem
I have a button with a onClick listener in a save person fragment which will save person data to the database. Everything works fine except for some reason with the first click it wont return me the inserted row ID but it will do so with 2nd click onwards.
I really need this ID in before proceeding to the next fragment.
Not sure if this is important but whenever I return (reload) to this save person fragment, the behaviour is always the same that the first click allways fails to capture the inserted row ID.
input data:
first name = John
last name = Smith
Just for demo purpose, if I will try to use this button 3x to insert the person data (returned insert ID is in the log), I will get all 3 rows in database with name John Smith, but the very first inserted row ID is not captured (default initialised value is 0), please see the log below:
Log
2020-10-19 12:49:20.320 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 0
2020-10-19 12:49:40.153 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 5
2020-10-19 12:49:40.928 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 6
EDITED ORIGINAL post
As suggested in the comments, I'm trying to go about the way of using LiveData and observer, but I'm still little bit stuck.
The setup
The below is the current setup.
Entity
#Entity(tableName = "person")
data class Person(
#PrimaryKey(autoGenerate = true)
val id: Int,
DAO
#Dao
interface PersonDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addPerson(person: Person): Long
Repository
class PersonRepository(private val personDao: PersonDao) {
val readAllPersonData: LiveData<List<Person>> = personDao.readAllPersonData()
suspend fun addPerson(person: Person): Long {
return personDao.addPerson(person)
}
ViewModel
I'm not sure if I'm doing things right at all here. I broke it down here in steps and created separate variables insertedPersonILiveData and insertedPersonId.
How could pass the returned row id to insertedPersonILiveData?
class PersonViewModel(application: Application) : AndroidViewModel(application) {
var insertedPersonILiveData: LiveData<Long> = MutableLiveData<Long>()
var insertedPersonId: Long = 0L
val readAllPersonData: LiveData<List<Person>>
private val repository: PersonRepository
init {
val personDao = ContactDatabase.getDatabase(application).personDao()
repository = PersonRepository(personDao)
readAllPersonData = repository.readAllPersonData
}
suspend fun addPerson(person: Person) = viewModelScope.launch {
insertedPersonId = repository.addPerson(person)
// ****************************************************************
// insertedPersonILiveData = insertedPersonId (what to do here) ???
// ****************************************************************
}
Save person fragment
This is the way I'm calling out the addPerson via modelView.
val person = Person(0, firstName, lastName)
lifecycleScope.launch {
personViewModel.addPerson(person)
}
Log.d("TEST_ADD_PERSON_ID","insertedPersonId: ${personViewModel.insertedPersonId}")
And this is the way I have done the observer (not sure if it's even correct).
val returnedIdListener: LiveData<Long> = personViewModel.insertedPersonILiveData
returnedIdListener.observe(viewLifecycleOwner, Observer<Long> { id: Long ->
goToAddContactFragment(id)
})
private fun goToAddContactFragment(id: Long) {
Log.d("TEST_ADD_PERSON_ID", "id: " + id)
}
Create database
#Database(
entities = [Person::class, Contact::class, ContactType::class],
views = [ContactDetails::class],
version = 1,
exportSchema = false
)
abstract class ContactDatabase : RoomDatabase() {
abstract fun personDao(): PersonDao
abstract fun contactTypeDao(): ContactTypeDao
abstract fun contactDao(): ContactDao
abstract fun contactDetailsDao(): ContactDetailsDao
companion object {
// For Singleton instantiation
#Volatile
private var instance: ContactDatabase? = null
fun getDatabase(context: Context): ContactDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): ContactDatabase {
return Room.databaseBuilder(context, ContactDatabase::class.java, "contacts_database")
.addCallback(
object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
WorkManager.getInstance(context).enqueue(request)
}
}
)
.build()
}
}
}
You're starting a coroutine to run addPerson, and then immediately calling Log with the current value of insertedPersonId in the viewmodel. The coroutine will run, insert the person, and update the VM with the ID of the inserted row, but that will happen long after your Log has run. Probably all of your results are actually the ID of the last record that was inserted.
I'm new to a lot of this too, but just based on what you have now, I think you just need to add
insertedPersonILiveData.value = insertedPersonId
in your addPerson function. That way you're updating that LiveData with a new value, which will be pushed to any valid observers. You've written some code that's observing that LiveData instance, so it should get the update when you set it.
edit your problem is that insertedPersonILiveData is the immutable LiveData type, so you can't set the value on it - it's read-only. You're creating a MutableLiveData object but you're exposing it as a LiveData type.
The recommended pattern for this is to create the mutable one as an internal object, expose a reference to it as an immutable type, and create a setter method that changes the value through the mutable reference (which it can access internally)
class myViewModel : ViewModel() {
// mutable version is private, all updates go through the setter function
// (the _ prefix is a convention for "private versions" of data fields)
private val _lastInsertedPersonId = MutableLiveData<Long>()
// we're making the instance accessible (for observing etc), but as
// the immutable LiveData supertype that doesn't allow setting values
val lastInsertedPersonId: LiveData<Long> = _lastInsertedPersonId
// setting the value on the MutableLiveData instance
// is done through this public function
fun setLastInsertedPersonId(id: Long) {
_lastInsertedPersonId.value = id
}
}
and then your observer would just call lastInsertedPersonId.observe, you don't need to copy the LiveData and observe that (like you're doing with returnedIdListener.
That's the basic pattern right there - internal MutableLiveData, exposed publicly as an immutable LiveData val, with a setter method to update the value. Everything outside the view model either observes the LiveData that's visible, or calls the setter method to update. Hope that makes sense! It's not that complicated once you get your head around what's basically going on

GSON Deserialization of subtypes in Kotlin

I'm not sure if this is a limitation, a bug or just bad use of GSON. I need to have a hierarchy of Kotlin objects (parent with various subtypes) and I need to deserialize them with GSON. The deserialized object has correct subtype but its field enumField is actually null.
First I thought this is because the field is passed to the "super" constructor but then I found out that "super" works well for string, just enum is broken.
See this example:
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory
open class Parent(val stringField: String,
val enumField: EnumField) {
enum class EnumField {
SUBTYPE1,
SUBTYPE2,
SUBTYPE3
}
}
class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2)
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type)
val subtypeRAF = RuntimeTypeAdapterFactory.of(Parent::class.java, "enumField")
.registerSubtype(Subtype1::class.java, Parent.EnumField.SUBTYPE1.name)
.registerSubtype(Subtype2::class.java, Parent.EnumField.SUBTYPE2.name)
.registerSubtype(Subtype3::class.java, Parent.EnumField.SUBTYPE3.name)
fun main() {
val gson = GsonBuilder()
.registerTypeAdapterFactory(subtypeRAF)
.create()
serializeAndDeserialize(gson, Subtype1()) // this works (but not suitable)
serializeAndDeserialize(gson, Subtype2("s2")) // broken
serializeAndDeserialize(gson, Subtype3("s3", Parent.EnumField.SUBTYPE3)) // broken
}
private fun serializeAndDeserialize(gson: Gson, obj: Parent) {
println("-----------------------------------------")
val json = gson.toJson(obj)
println(json)
val obj = gson.fromJson(json, Parent::class.java)
println("stringField=${obj.stringField}, enumField=${obj.enumField}")
}
Any ideas how to achieve to deserialization of enumField?
(deps: com.google.code.gson:gson:2.8.5, org.danilopianini:gson-extras:0.2.1)
P.S.: Note that I have to use RuntimeAdapterFactory because I have subtypes with different set of fields (I did not do it in the example so it is easier to understand).
Gson requires constructors without arguments to work properly (see deep-dive into Gson code below). Gson constructs raw objects and then use reflection to populate fields with values.
So if you just add some argument-less dummy constructors to your classes that miss them, like this:
class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2) {
constructor() : this("")
}
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type) {
constructor() : this("", EnumField.SUBTYPE3)
}
you will get the expected output:
-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3
Gson deep-dive
If you want to investigate the internals of Gson, a tip is to add an init { } block to Subtype1 since it works and then set a breakpoint there. After it is hit you can move up the call stack, step through code, set more breakpoints etc, to reveal the details of how Gson constructs objects.
By using this method, you can find the Gson internal class com.google.gson.internal.ConstructorConstructor and its method newDefaultConstructor(Class<? super T>) that has code like this (I have simplified for brevity):
final Constructor<? super T> constructor = rawType.getDeclaredConstructor(); // rawType is e.g. 'class Subtype3'
Object[] args = null;
return (T) constructor.newInstance(args);
i.e. it tries to construct an object via a constructor without arguments. In your case for Subtype2 and Subtype3, the code will result in a caught exception:
} catch (NoSuchMethodException e) { // java.lang.NoSuchMethodException: Subtype3.<init>()
return null; // set breakpoint here to see
}
i.e. your original code fails since Gson can't find constructors without arguments for Subtype2 and Subtype3.
In simple cases, the problem with missing argument-less constructors is worked around with the newUnsafeAllocator(Type, final Class<? super T>)-method in ConstructorConstructor, but with RuntimeTypeAdapterFactory that does not work correctly.
I may be missing something in what you're trying to achieve, but is it necessary to use the RuntimeTypeAdapterFactory? If we take out the line where we register that in the Gson builder, so that it reads
val gson = GsonBuilder()
.create()
Then the output returns the enum we would expect, which looks to be serialising / deserialising correctly. I.e. the output is:
-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3
It also may be an idea to implement Serializable in Parent. i.e.
open class Parent(val stringField: String, val enumField: EnumField) : Serializable {
enum class EnumField {
SUBTYPE1,
SUBTYPE2,
SUBTYPE3
}
}
Try adding #SerializedName annotation to each enum.
enum class EnumField {
#SerializedName("subtype1")
SUBTYPE1,
#SerializedName("subtype2")
SUBTYPE2,
#SerializedName("subtype3")
SUBTYPE3
}

Jackson deserialization - Kotlin data classes - Defaults for missing fields per mapper

Given this data class:
data class MyPojo(val notInJson: Int, val inJson: Int)
Assume I want to implement a function of the form:
fun deserialize(jsonString: String, valueForFieldNotInJson: Int): MyPojo
Where jsonString does not include a field named notInJson. Assume also, that I have no control over MyPojo class definition.
How could I use Jackson library to deserialize MyPojo from jsonString and augment the missing field (notInJson) from valueForFieldNotInJson parameter?
Notes:
Basically, the question is about deserializing a Immutable class, where some fields come from Json and others are supplied at runtime.
Using custom deserializers or builders will not work because missing values are unknow at compile time.
This can be achieved by combining MinInAnnotations and ValueInjection.
Complete solution as follows:
data class MyPojo(val notInJson: Int, val inJson: Int)
class MyPojoMixIn {
#JacksonInject("notInJson") val notInJson: Int = 0
}
fun deserialize(jsonString: String, valueForFieldNotInJson: Int): MyPojo {
val injectables = InjectableValues.Std().addValue("notInJson", valueForFieldNotInJson)
val reader = jacksonObjectMapper()
.addMixIn(MyPojo::class.java, MyPojoMixIn::class.java)
.readerFor(MyPojo::class.java)
.with(injectables)
return reader.readValue(jsonString)
}