moshi kotlin non constructor properties not serialized - android

I have a class defined like this:
data class Medicalnfo(
#Json(name = "allergies") val allergies: List<Allergy>? = null
): ProfileModel(){
#Json(name = "has_allergies") val hasAllergies: Boolean = allergies != null && allergies.isNotEmpty()
}
The problem is that hasAllergies is not included in the json serialization generated by Moshi. How can this be achieved?
Here is the test
#Test
fun medicalInfoJsonContainsFlags(){
val adapter = moshi.adapter(Medicalnfo::class.java)
val jsonStr = adapter.toJson(Medicalnfo(allergies = listOf())) //value is {"allergies":[]}
assert(jsonStr.contains("has_allergies"))
}

If you make hasAllergies as var it will work
data class Medicalnfo(
#Json(name = "allergies") val allergies: List<Allergy>? = null
): ProfileModel(){
#Json(name = "has_allergies") var hasAllergies: Boolean = allergies != null && allergies.isNotEmpty()
}

Related

Turn String of object back into object Kotlin

I have an object that I wrote to a file and now I want to take that string I get from the file to turn it back into an object for me to use.
Object class:
#Serializable
class DrinkItem {
#SerializedName("strAlcoholic")
val alcoholic: String? = null
#SerializedName("strIngredient1")
val ingredient1: String? = null
#SerializedName("strIngredient10")
val ingredient10: String? = null
#SerializedName("strIngredient11")
val ingredient11: String? = null
#SerializedName("strIngredient12")
val ingredient12: String? = null
#SerializedName("strIngredient13")
val ingredient13: String? = null
#SerializedName("strIngredient14")
val ingredient14: String? = null
#SerializedName("strIngredient15")
val ingredient15: String? = null
#SerializedName("strIngredient2")
val ingredient2: String? = null
#SerializedName("strIngredient3")
val ingredient3: String? = null
#SerializedName("strIngredient4")
val ingredient4: String? = null
#SerializedName("strIngredient5")
val ingredient5: String? = null
#SerializedName("strIngredient6")
val ingredient6: String? = null
#SerializedName("strIngredient7")
val ingredient7: String? = null
#SerializedName("strIngredient8")
val ingredient8: String? = null
#SerializedName("strIngredient9")
val ingredient9: String? = null
#SerializedName("strInstructions")
val instructions: String? = null
#SerializedName("strMeasure1")
val measurement1: String? = null
#SerializedName("strMeasure10")
val measurement10: String? = null
#SerializedName("strMeasure11")
val measurement11: String? = null
#SerializedName("strMeasure12")
val measurement12: String? = null
#SerializedName("strMeasure13")
val measurement13: String? = null
#SerializedName("strMeasure14")
val measurement14: String? = null
#SerializedName("strMeasure15")
val measurement15: String? = null
#SerializedName("strMeasure2")
val measurement2: String? = null
#SerializedName("strMeasure3")
val measurement3: String? = null
#SerializedName("strMeasure4")
val measurement4: String? = null
#SerializedName("strMeasure5")
val measurement5: String? = null
#SerializedName("strMeasure6")
val measurement6: String? = null
#SerializedName("strMeasure7")
val measurement7: String? = null
#SerializedName("strMeasure8")
val measurement8: String? = null
#SerializedName("strMeasure9")
val measurement9: String? = null
#SerializedName("strDrink")
val name: String? = null
#SerializedName("strDrinkThumb")
val thumbnail: String? = null
}
I use this function to write the object to a file:
private fun writeToFile(fileName: String, byteArray: ByteArray){
val lineSeparator: String = System.getProperty("line.separator") as String
// File
val path = context!!.filesDir
val directory = File(path, "LET")
directory.mkdirs()
val file = File(directory, fileName)
//append drink to file
FileOutputStream(file, true).use {
it.write(byteArray)
it.write(lineSeparator.toByteArray())
}
}
After the function is done with the object it is turned into this string:
{"alcoholic":"Alcoholic","ingredient1":"Apricot brandy","ingredient2":"Triple sec","ingredient3":"Lime","ingredient4":"Lime","instructions":"Shake all ingredients (except lime wedge) with ice and strain into a cocktail glass. Add the wedge of lime and serve.","measurement1":"1 oz ","measurement2":"1 oz ","measurement3":"Juice of 1 ","measurement4":"1 ","name":"After Dinner Cocktail","thumbnail":"https://www.thecocktaildb.com/images/media/drink/vtytxq1483387578.jpg"}
Is there a function or library that would help me turn a string of an object into said object?
Are you using library to convert your DrinkItem to the outputting JSON String? Seems that the keys in your JSON String do not match with what you have named in #SerializedName().
You can use the Gson library to handle JSON String and Object conversion.
For example, if you have the same DrinkItem class, you can convert your DrinkItem into JSON String as following:
// Define DrinkItem and set some of the attributes
val drinkItemTest = DrinkItem()
drinkItemTest.alcoholic = "Alcohol One"
drinkItemTest.ingredient1 = "Ingredient One"
// Use Gson library to convert Object to JSON String
val drinkItemTestString = Gson().toJson(drinkItemTest)
println(drinkItemTestString)
Output
{"strAlcoholic":"Alcohol One","strIngredient1":"Ingredient One"}
And to convert your JSON String back to DrinkItem, you can do something like this:
// Read the whole JSON String from your file here
val drinkItemString = "{\"strAlcoholic\":\"Alcohol One\",\"strIngredient1\":\"Ingredient One\"}"
// And make use of Gson library to convert your JSON String into DrinkItem Object
val drinkItem = Gson().fromJson(drinkItemString, DrinkItem::class.java)

How to get my retrofit2 model to parse this?

This is unusuall response where the name of object is the same as the object's ID and at this point I don't really know how to parse this response
"addresses": {
"163492": {
"address_id": "163492",
//more of String variables
},
"166127": {
"address_id": "166127",
//more of String variables
},
"166202": {
"address_id": "166202",
//more of String variables
}
}
this is how my Event model looks like, I'm using room database to save this response later
#Entity
data class Event(
#PrimaryKey(autoGenerate = false)
#SerializedName("id") val id: Int,
#SerializedName("title") val title: String,
#SerializedName("description") val desc: String,
#SerializedName("note") val note: String? = null,
#SerializedName("date") val dateTs: Long,
#SerializedName("begintime") val beginTime: String,
#SerializedName("enddate") val endDate: String,
#SerializedName("endtime") val endTime: String,
#SerializedName("customerid") val customerId: String? = null,
#SerializedName("address_id") val addressId: String? = null,
#SerializedName("pin") val pin: String? = null,
#SerializedName("location") val location: String? = null,
#SerializedName("customerlocation") val customerLocation: String? = null,
#field:TypeConverters(beskidmedia.pl.scanner.room.TypeConverters::class)
#SerializedName("nodes") val nodes: List<Node>? = null,
#SerializedName("closed") val closed: Int,
#SerializedName("type") val type: Int,
#SerializedName("ticketid") val ticketId: String? = null,
#SerializedName("customername") val customerName: String? = null,
#field:TypeConverters(beskidmedia.pl.scanner.room.TypeConverters::class)
#SerializedName("contacts") val contacts: List<Contacts>? = null,
#field:TypeConverters(beskidmedia.pl.scanner.room.TypeConverters::class)
#SerializedName("addresses") val addresses: List<Address>? = null,
#Embedded
#SerializedName("assignments") val assignments: Assignments? = null,
#SerializedName("lastUpdate") val lastUpdate: Long = System.currentTimeMillis()
)
everything beside the addresses part is fine cos I tested it using response with null for addresses, I tried to do deserializer for this but it appears like it don't recognise it, this is how it looks like
class EventDeserializer : JsonDeserializer<Event> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): Event {
json?.asJsonObject!!.let { event ->
val nodes = mutableListOf<Node>()
val contacts = mutableListOf<Contacts>()
val addresses = mutableListOf<Address>()
val net = mutableListOf<Assignment>()
val tv = mutableListOf<Assignment>()
val assignments = Assignments(net, tv)
val netTemp = event.get("assignments").asJsonObject.get("assignments_net").asJsonArray
val tvTemp = event.get("assignments").asJsonObject.get("assignments_tv").asJsonArray
netTemp.forEach { assignment ->
assignment.asJsonObject.let {
net.add(
Assignment(
name = it.get("name").asString,
id = it.get("id").asInt
)
)
}
}
tvTemp.forEach { assignment ->
assignment.asJsonObject.let {
tv.add(
Assignment(
name = it.get("name").asString,
id = it.get("id").asInt
)
)
}
}
val nodesTemp = event.get("nodes").asJsonArray
nodesTemp.forEach { node ->
node.asJsonObject.let {
nodes.add(
Node(
id = it.get("id").asInt,
name = it.get("name").asString,
mac = it.get("mac").asString,
ip = it.get("ip").asString,
location = it.get("location").asString,
netName = it.get("netname").asString
)
)
}
}
val contactsTemp = event.get("contacts").asJsonArray
contactsTemp.forEach { contact ->
contact.asJsonObject.let {
contacts.add(
Contacts(
phone = it.get("phone").asString,
contact = it.get("contact").asString,
name = it.get("name").asString,
type = it.get("type").asString,
typeStr = it.get("typestr").asString
)
)
}
}
val addressesTemp = event.get("addresses").asJsonObject
addressesTemp?.keySet()?.let { names ->
names.forEach { name ->
addressesTemp.get(name).asJsonObject.let {
addresses.add(
Address(
id = it.get("address_id").asString,
name = it.get("location").asString
)
)
}
}
}
return Event(
id = event.get("id").asInt,
title = event.get("title").asString,
desc = event.get("description").asString,
note = event.get("note")?.asString,
dateTs = event.get("date").asLong,
beginTime = event.get("begintime").asString,
endDate = event.get("enddate").asString,
endTime = event.get("endtime").asString,
customerId = event.get("customerid")?.asString,
addressId = event.get("address_id")?.asString,
pin = event.get("pin")?.asString,
location = event.get("location")?.asString,
customerLocation = event.get("customerlocation")?.asString,
nodes = nodes,
closed = event.get("closed").asInt,
type = event.get("type").asInt,
ticketId = event.get("ticketid")?.asString,
customerName = event.get("customername")?.asString,
contacts = contacts,
addresses = addresses,
assignments = assignments
)
}
}
}
and this is how I'm creating gson factory
val gson = GsonBuilder().registerTypeAdapter(Event::class.java, EventDeserializer())
Retrofit
.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(
GsonConverterFactory.create(gson.create())
)
.callbackExecutor(Executors.newSingleThreadExecutor())
and the structure of response looks like this
Call<List<Event>>
but the list always have 1 element and this is artifact of old api that i can't change
Ok, so I figured this out, apparently your deserializer needs to be the exactly the same type as your response, so I added the interceptor that removed excess array that wrapped every response and now deserializer is being ussed as intended.

Create an AdapterFactory for gson for dynamic Types

I have a JSON string that I need to converted to data class object in Kotlin, the problem is that there is a field (details) that can have a different structure depending of the value of another field like this
val jsonString1 = "{'name': 'Juan', 'phase': 'step1', 'details': { 'name': 'product 1' }}"
val jsonString2 = "{'name': 'Juan', 'phase': 'step2', 'details': { 'position': 10 }}"
now I have something like
data class Customer(
var name: String? = null
var phase: String? = null
var details: Details? = null
)
data class Details(
var name: String? = null
)
data class Details2(
var position: Int? = null
)
now with gson I know I can
Gson().fromJson(jsonString1, Customer::class.java)
I want to be able to automatically use the right data class depending on the value of the phase field, I know I can create an adapterFactory, but I can't figure out how, an in kotlin is worse
I was reading this post
http://anakinfoxe.com/blog/2016/02/01/gson-typeadapter-and-typeadapterfactory/
and I'm pretty sure is the way to go, but I can't quite get it
Yep, it's pretty easy to write such adapter. I've slightly changed your example:
data class Customer(
var name: String? = null,
var phase: String? = null,
var details: Details? = null
)
sealed class Details {
data class Details1(var name: String? = null) : Details()
data class Details2(var position: Int? = null) : Details()
}
class CustomerDeserializer : JsonDeserializer<Customer> {
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Customer {
val customerObject = json.asJsonObject
val detailsObject = customerObject.getAsJsonObject("details")
val details = if (detailsObject.has("name")) {
Details.Details1(detailsObject.get("name").asString)
} else {
Details.Details2(detailsObject.get("position").asInt)
}
return Customer(
name = customerObject.get("name").asString,
phase = customerObject.get("phase").asString,
details = details
)
}
}
fun main() {
val gson = GsonBuilder()
.registerTypeAdapter(Customer::class.java, CustomerDeserializer())
.create()
println(gson.fromJson(jsonString1, Customer::class.java))
println(gson.fromJson(jsonString2, Customer::class.java))
}
data class Customer(
var name: String? = null
var phase: String? = null
var details: Details? = null
)
data class Details(
var name: String? = null
var position: Int? = null
)
Define Details class in this way
Gson().fromJson(jsonString1, Customer::class.java)
return a Customer either name is null or position is null

Can't convert JSONArray into a list (Kotlin)

This is my string:
{"array":[{"message":"test1","name":"test2","creation":"test3"},{"message":"test1","name":"test2","creation":"test3"}]}
And I want it get that array into a list of object in Kotlin app for Android.
I tried to do it using two examples from this site... So here is my code (res = that string):
val gson = Gson()
val obj = gson.fromJson(res, JsonObject::class.java)
val arr = obj.getAsJsonArray("array")
println(arr.toString())
val list1 : List<JThread> = gson.fromJson(arr, object : TypeToken<List<JThread>>() {}.type)
val list2 = gson.fromJson(arr, Array<JThread>::class.java).asList()
for (x in list1){
println(x.message)
}
for (x in list2){
println(x.message)
}
However I'm only getting null in x.message. I don't know what can go wrong.
I also tried changing arr to arr.toString() everywhere and that didn't work either.
Also JThread is:
object JThread {
var message: String? = null
var name: String? = null
var creation: String? = null }
This can be done without GSON or any other third party library:
#Throws(JSONException::class)
fun JSONObject.toMap(): Map<String, Any> {
val map = mutableMapOf<String, Any>()
val keysItr: Iterator<String> = this.keys()
while (keysItr.hasNext()) {
val key = keysItr.next()
var value: Any = this.get(key)
when (value) {
is JSONArray -> value = value.toList()
is JSONObject -> value = value.toMap()
}
map[key] = value
}
return map
}
#Throws(JSONException::class)
fun JSONArray.toList(): List<Any> {
val list = mutableListOf<Any>()
for (i in 0 until this.length()) {
var value: Any = this[i]
when (value) {
is JSONArray -> value = value.toList()
is JSONObject -> value = value.toMap()
}
list.add(value)
}
return list
}
Usage to convert JSONArray to List:
val jsonArray = JSONArray(jsonArrStr)
val list = jsonArray.toList()
Usage to convert JSONObject to Map:
val jsonObject = JSONObject(jsonObjStr)
val map = jsonObject.toMap()
More info is here
Use this code:
import com.google.gson.annotations.SerializedName
import com.google.gson.Gson
data class Array(
#SerializedName("message")
var message: String,
#SerializedName("name")
var name: String,
#SerializedName("creation")
var creation: String
)
data class Example(
#SerializedName("array")
var array: List<Array>? = null
)
private fun fromJson(json:String):Example{
return Gson().fromJson<Example>(json, Example::class.java)
}
PS: I made it with this site:http://www.jsonschema2pojo.org/

Android Kotlin save data class in Firebase Database

I am working on an Android application in Kotlin which integrate Firebase.
Now I want to store my data (Kotlin data class) into Firebase Database.
Data Classes:
#Parcelize
data class Trip(
val fromAddress: String,
val toAddress: String,
val fromLocation: String,
val toLocation: String,
val orderUid: String
) : Parcelable
#Parcelize
data class Order(val trip: Trip, val date: Date, var status: OrderStatus, val userUid: String) : Parcelable {
var pickUpDate: Date? = null
var dropOffDate: Date? = null
var price: Double? = null
}
Fireabase Database write operation:
fun createNewOrder(
fromAddress: String,
toAddress: String,
fromLocation: Location,
toLocation: Location
) {
val fromGeoLocation = fromLocation.convertToGeoLocation()
val toGeoLocation = toLocation.convertToGeoLocation()
val userUid = sharedPreferences[CURRENT_USER_UID_KEY, ""]!!
val orderKey = databaseReference.child(DB_ORDERS_KEY).push().key
val tripKey = databaseReference.child(DB_TRIPS_KEY).push().key
val trip = orderKey?.let { createNewTrip(fromAddress, toAddress, it) }
val order = trip?.let { Order(it, Date(), OrderStatus.PENDING, userUid) }
if (trip != null && order != null && !userUid.isNullOrEmpty()) {
ordersGeoFire.setLocation(trip.fromGeoLocation, fromGeoLocation)
ordersGeoFire.setLocation(trip.toGeoLocation, toGeoLocation)
val allData = mutableMapOf<String, Any>()
allData["/$DB_TRIPS_KEY/$tripKey"] = trip?.convertToMap()
allData["/$DB_ORDERS_KEY/$orderKey"] = order?.convertToMap()
allData["/$DB_USERS_KEY/$userUid/$DB_ORDERS_KEY/$orderKey"] = true
databaseReference.updateChildren(allData)
}
}
I received this error:
com.google.firebase.database.DatabaseException: No properties to serialize found on class kotlin.Unit
Any suggestions?
The problem in your code is that the fileds inside your Trip class are not initialized. A recommended way in which you can create your model class would be:
class Trip(
val displayName: String = "",
val email: String = "",
val photoUrl: String = "",
val userId: String = ""
)
This is only what you need. And a way to create a new object of your Trip class, would be:
val trip = Trip(displayName, email, photoUrl, userId)
It was my mistake, because I was forget to add return type in my extensions convertToMap functions. Now they look like this:
fun Trip.convertToMap(): MutableMap<String, Any> {
val map = mutableMapOf<String, Any>()
map["fromAddress"] = fromAddress
map["toAddress"] = toAddress
map["fromGeoLocation"] = fromGeoLocation
map["toGeoLocation"] = toGeoLocation
map["orderUid"] = orderUid
return map
}
And also thanks to #Alex Mamo for his answer, it helps me in my investigation.
Now my code looks like this and works fine:
#Parcelize
data class Trip(
var fromAddress: String = "",
var toAddress: String = "",
var fromGeoLocation: String = "",
var toGeoLocation: String = "",
var orderUid: String = ""
) : Parcelable
#Parcelize
data class Order(
var trip: Trip? = null,
var date: Date? = null,
var status: OrderStatus? = null,
var userUid: String = ""
) : Parcelable {
var pickUpDate: Date? = null
var dropOffDate: Date? = null
var price: Double? = null
}
fun createNewOrder(
fromAddress: String,
toAddress: String,
fromLocation: Location,
toLocation: Location
): LiveData<Order> {
orderLiveData = MutableLiveData()
orderLiveData.value = null
val userUid = sharedPreferences[CURRENT_USER_UID_KEY, ""]!!
val orderKey = databaseReference.child(DB_ORDERS_KEY).push().key
val tripKey = databaseReference.child(DB_TRIPS_KEY).push().key
val trip = orderKey?.let { createNewTrip(fromAddress, toAddress, fromLocation, toLocation, it) }
val order = trip?.let { Order(it, Date(), OrderStatus.PENDING, userUid) }
if (trip != null && order != null && !userUid.isNullOrEmpty()) {
val allData = mutableMapOf<String, Any>()
allData["/$DB_TRIPS_KEY/$tripKey"] = trip.convertToMap()
allData["/$DB_ORDERS_KEY/$orderKey"] = order.convertToMap()
allData["/$DB_USERS_KEY/$userUid/$DB_ORDERS_KEY/$orderKey"] = true
databaseReference.updateChildren(allData) { databaseError, databaseReference ->
if (databaseError == null) orderLiveData.value = order
}
}
return orderLiveData
}
Hope this will be helpful

Categories

Resources