Serialization with GSON in a single Entity class - android

I have a JSON with a parent called "posts" and I have the respective children. I am using GSON to serialize the fields into my entity(I'am using Room).
I am using a POJO called Post like
#Entity
data class Post(
#SerializableName("somethig") val something:Int
#SerializableName("somethig") val something:Int
)
And I am using a class with a List of it
class PostParent{
#Serializablename("posts") listPosts : List<Post> //to call the parent
}
Is there any other way to pass the parent JSON name directly in the Entity class instead of doing a class with a single List? I want to avoid this because I have a dao where I need to insert and fetch the data and he only accepts Lists, so I cant have my dao method return the class
Method
override fun loadFromDb(): LiveData<PostParent> { //error in build time
return postsDao.getPosts() //tried to do postsDao.getPosts().data already
}

Try doing this. Have not tried it myself yet, so you might need to edit some syntax here n there.
#Entity
class PostParent{
#Serializablename("posts") listPosts : List<#Embedded Post>
}
data class Post(
#SerializableName("somethig") val something:Int
#SerializableName("somethig") val something:Int
)

Related

How to make base class accept generics which have different key

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>>
}

Kotlin/Room/Android Error: Getting error while Querying for nested object which can have null fields

I am trying to request data from an API which responds with an array of nested JSON object in which some entries of the object can be null.
Each of the object has a structure similar to the following :
data class A(
#ColumnInfo("item1InA")
val item1InA:Double?=null,
#ColumnInfo("item2InA")
val item2InA:Double?=null,
)
data class B(
#ColumnInfo("item1InB")
val item1InB:Double?=null,
#ColumnInfo("item2InB")
val item2InB:Double?=null,
)
#Entity(tableName="temp_table")
data class Combination(
#PrimaryKey
#NonNull
#ColumnInfo("key")
val key:String='-',
#ColumnInfo("originalItem1")
val originalItem1:String='-',
#ColumnInfo("originalItem2")
val originalItem2:String='-',
#Embedded
val a:A=A(),
#Embedded
val b:B=B(),
)
I am querying the database in the application to get the list of the all Combination objects using the following interface :
#Dao
interface CombinationDao{
#Query("SELECT * FROM temp_table")
suspend fun getAll():List<Combination>
}
The issue is that i am getting the error: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter
When I checked on the directed links to the CombinationDao_Impl.java file, I got the following error: both methods have same erasure, yet neither overrides the other
I don't understand what I am doing wrong here. Can anyone help me get on the right track?
Sample JSON
{
"A":{
"item1InA":23.0,
"item2InA":null,
},
"B":{
"item1InB":67327.309,
"item2InB":null,
},
"key":"thisIsUnique",
"originalItem1":"hello",
"originalItem2":"world",
}
According to the documentation at Embedded | Android Developers,
When an embedded field is read, if all fields of the embedded field (and its sub fields) are null in the Cursor, it is set to null. Otherwise, it is constructed
The issue with my code was that for some JSON objects each of the fields of the embedded objects were null causing room to simply ignore to construct the object making the variables a and b to point at null.
Changing the code of the entity class to,
data class Combination(
#PrimaryKey
#NonNull
#ColumnInfo("key")
val key:String='-',
#ColumnInfo("originalItem1")
val originalItem1:String='-',
#ColumnInfo("originalItem2")
val originalItem2:String='-',
#Embedded
val a:A?=A(),
#Embedded
val b:B?=B(),
)
solved the issue for me.

How can I update a TypeConverted column of an entity in Room Dao function

I have an #Entity which holds a variable(list of custom object) along with other fields for the table.
I am able to insert, fetch and delete from this entity.
But I am facing an issue in updating the entity:
I want to update that particular field which holds a list of custom object in the table but while compilation it throws an error:
error: Query method parameters should either be a type that can be converted into a
database column or a List / Array that contains such type. You can consider adding a Type Adapter for this.
I could update the complete row object but the problem lies in updating this single field. I am using TypeConverters on my #Database class but I have tried using them on Dao and the update function itself but it reports the same error.
Can someone please help me to update this particular field in the row, I don't want to provide the full object of this entity for this to happen.
My Entity is:
#Entity data class TableName(
#PrimaryKey
var id: String = "",
#SerializedName("varOne")
#Expose
var varOne: List<CustomObjects>? = null)
Update method is something like this:
#TypeConverters(MyTypeConverters.VarOneListTypeConverters::class)
#Query("Update TableName SET varOne = :varOneList")
abstract fun updateTableName(varOneList: List<CustomObjects>)
Ideally you should try to model this as a relationship with a separate table for the CustomObject and a foreign key ref to the primary key of TableName.
However you can still write a converter for the type List<CustomObject>. Room only understands the Sqlite Data types and any other type needs to be converted to one of they types that room understands. They have provided the TypeConverter annotations for the same. If you use Gson to serialize your CustomObject then you can use the following converter. The code is self explanatory
public class Converters {
#TypeConverter
public static ArrayList<String> fromString(String value) {
Type listType = new TypeToken<ArrayList<CustomObject>>() {}.getType();
return new Gson().fromJson(value, listType);
}
#TypeConverter
public static String fromArrayList(ArrayList<CustomObject> list) {
Gson gson = new Gson();
String json = gson.toJson(list);
return json;
}
}
And you just have to add this converter to your Database class
#TypeConverters(Converters::class)
abstract class YourDatabase extends RoomDatabase
You can directly insert the string. Typeconverter doesn't seem to be working with Update query.
#Query("Update TableName SET varOne = :varOneList")
abstract fun updateTableName(varOneList: String)
When you call this method do the conversion manually
tableDao.updateTableName(Gson().toJson(varOneList))
Here I'm using Gson to do the conversion, you can use whatever you are using in your converter class.
Ideally, you should structure your tables such that you don't have to insert a List as String

How to implement polymorphic list deserialization with GSON in Kotlin?

I have a data feed that is returning a list that could be either of three types ( Say type A, B and C ). All of the types above share 2 properties, the rest of the properties is specific to the type. I tried using the pattern.
abstract class Parent (val type: String, val id: String)
And
data class TypeA(override val type: String ... )
data class TypeB(override val type: String ... )
I am using Retrofit and trying to deserialize the list to
List<? extends Parent>
which in Kotlin should be
List<out Parent>
However GSON is throwing a deserializing error on instantiating the parent class which is abstract.
java.lang.RuntimeException: Failed to invoke public com.XX.Parent() with no args
Any ideas how I can implement this in Kotlin?
As you have Moshi tagged in your question, I'll give you a way of doing it using MOshi's PolymorphicJsonAdapterFactory. You can basically parse something into different types, depending on the value of the object's property.
First thing you'll do is declared your parent type as a sealed class and have the other types extend from it:
sealed class Parent(val type: String){
data class TypeA(override val type: String, ... ): Parent(type)
data class TypeB(override val type: String, ... ): Parent(type)
}
now you're gonna tell Moshi how to parse Parent objects. You do that registering a PolymorphicJsonAdapterFactory:
val moshi = Moshi.Builder()
.add(PolymorphicJsonAdapterFactory.of(Parent::class.java, "type")
.withSubtype(TypeA::class.java, "typeA")
.withSubtype(TypeB::class.java, "typeB")
.build()
with that, if the value of the property "type" is "typeA", it will deserialize into a TypeA instance. like wise to TypeB, if property "type" is "typeB"
You can look another example here:
https://github.com/square/moshi/blob/master/adapters/src/main/java/com/squareup/moshi/adapters/PolymorphicJsonAdapterFactory.java

Kotlin and ObjectBox: Relations in Data Classes

How can I initialize a Data Class with a ToOne relation? For example, I have the two Data Classes below:
#Entity
data class EntityA(
#Id var id: Long,
var entityB: ToOne<EntityB>
)
#Entity
data class EntityB(
#Id var id: Long
)
Now, I want to initialize EntityA like that:
var e = EntityA(1, EntityB())
But, obviously, I can't do that because the second argument is a ToOne and not an EntityB. I tried to instantiate ToOne but its constructor wants a second argument that I don't know how to create.
Do not put the relation in the primary constructor. Then you can use a secondary constructor to call toOne.target = entity. It should look like something like this:
#Entity
data class EntityA(#Id var id: Long) {
lateinit var entityB: ToOne<EntityB>
constructor(b: EntityB) : this(0) {
entityB.target = b
}
}

Categories

Resources