moshi dynamic polymorphic adapter from string - android

In Gson I can do something like this:
//mapItem - item of map with info for which class should string be deserialized
BaseReqClass req = (BaseReqClass) gson.fromJson(jsonString, mapItem.getValue());
req.doSomeStuff()
where
class AReq : BaseReqClass()
class BReq : BaseReqClass()
I'm selecting mapItem based on map key, which also clearly define me a subclass which moshi should istantiate (mapItem.getValue()).
What should I do to get the same behavior with Moshi?
I know that it is PolymorphicJsonAdapterFactory but I don’t want to have a special field in my json.

Ok, I found solution.
I know what should I do because of mapItem.
So I've implemented something like this:
val rsp =
"{\"token\":\"ABC\",\"data\":{\"id\":\"1234\",\"hash\":\"9N6PpUW9H8T6tuEc1wcvWu\"},\"locale\":\"EN\"}";
//val rsp = "{\"token\":\"FGH\",\"data\":{\"somefield\":\"someString\",\"otherid\":\"555\"},\"locale\":\"EN\"}"
val factory: JsonAdapter.Factory
if (getServiceName() == "areq") {
factory = ResultJsonAdapterFactory.of(BaseReqClass::class.java)
.withSubtype(AReq::class.java)
} else {
factory = ResultJsonAdapterFactory.of(BaseReqClass::class.java)
.withSubtype(BReq::class.java)
}
val moshi = Moshi.Builder()
.add(factory)
.build()
val jsonAdapter = moshi.adapter(BaseReqClass::class.java)
val req = jsonAdapter.fromJson(rsp)
println("serialized: ${jsonAdapter.toJson(req)}")
I can swap rsp from comment on top and moshi deserialize data to second class object.

Related

Gson didnt support MutableLiveData <model> in kotlin

I have the following code, I receive the following exception when I deserilize the response to a Gson from JSON:
java.lang.ClassCastException: com.test.model.photos.photosModel cannot be cast to androidx.lifecycle.MutableLiveData
This is my My code when I receive the response from the server:
lateinit var _photosModel:MutableLiveData<photosModel>
val gson = Gson()
val modelObj = gson.fromJson<Any>(response.toString(), photosModel::class.java)
_photosModel = modelObj as MutableLiveData<photosModel>
Simply because you are parsing from Json to model is Any that while you are needing type MutableLiveData.
And more thing, if you want to set data for MutableLiveData, you should use 2 methods setValue() if you want to set value in main thread and postValue() if you want to set value in other thread.
Just like that:
YourViewModel.kt
private val _photosModel = MutableLiveData<PhotosModel>()
val photosModel: LiveData<PhotosModel> = _photosModel
val gson = Gson()
val modelObj: PhotosModel = gson.fromJson(response.toString(), PhotosModel::class.java) // You should make the first letter of the class uppercase.
// Set value for mutable livedata
_photosModel.value = modelObj

Moshi adapter creation failure: "requires explicit JsonAdapter to be registered"

var wall= ArrayList<VKWall>()
try {
val response = r.getString("response") as String
val moshi = Moshi.Builder().build()
val type: Type = Types.newParameterizedType(
ArrayList::class.java,
VKWall::class.java
)
val jsonAdapter: JsonAdapter<ArrayList<VKWall>> = moshi.adapter(type)
wall = jsonAdapter.fromJson(response)!!
} catch (e: JSONException){}
return wall
It can not create adapter. Debuger can`t execute this string and goes to exception of function over this code
val jsonAdapter: JsonAdapter<ArrayList<VKWall>> = moshi.adapter(type)
I`am trying to do everything like there
https://github.com/square/moshi
Platform java.util.ArrayList<com.e.app.fragments.vk_tabs.WallFragment.DataPackage.VKWall> (with no annotations) requires explicit JsonAdapter to be registered
#Parcelize
#JsonClass(
generateAdapter = true)
data class VKWall (
// val UserName:String="",
// val UserSurname:String="",
#Json(name = "text")
val Text:String="" ,
// val attachments: Attachments?,
// val copyright: String="",
// val repost: Repost?
):Parcelable
{
}
The problem is in that moshi don't have adapter for yours class VKWall. To resolve this you could add KotlinJsonAdapterFactory that based on reflection:
val moshi = Moshi.Builder()
// ... add your own JsonAdapters and factories ...
.add(KotlinJsonAdapterFactory())
.build()
Or you could use generated adapter like this:
// Annotate yours class #JsonClass(generateAdapter = true)
#JsonClass(generateAdapter = true)
class VKWall(
....
)
More documentation about this https://github.com/square/moshi#kotlin
More about yours problem https://www.zacsweers.dev/a-closer-look-at-moshi-1-9/
Now, for Kotlin classes, you either need to use code gen,
KotlinJsonAdapterFactory, or supply your own custom JsonAdapter.
This is a potentially dangerous change! In projects using code gen,
there are cases where Kotlin classes could have (appeared to) Just
Work™️ before if you forgot to annotate them with #JsonClass. These
will fail at runtime now. If you're worried about this, I suggest
using Moshi 1.9 only in debug builds for a period of time to tease
these out before releasing production builds with it.

Moshi and Retrofit cannot parse simple JSON

I am trying to parse this JSON:
{
"random number1":
{AT=
{av=-54.697, ct=320206.0, mn=-92.47, mx=0.495},
First_UTC=2020-05-17T14:54:38Z
},
"random number2":
{AT=
{av=-54.6437, ct=3204306.0, mn=-92.47, mx=0.495},
First_UTC=2020-05-17T14:54:43Z
}
}
I have made a simple adapter class for this
#FromJson
fun fromJson(json: Map<String, Any>): OuterData {
var count = 0
var moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
var adapter = moshi.adapter<InnerData>(InnerData().javaClass)
json.entries.forEach {
OuterData.innerData[count++] = adapter.fromJson(test)!!
}
}
class InnerData() {
var AT = DataDetail()
var First_UTC = ""
}
It properly iterates over each "random number" entry and the inner objects all look correct but for some reason I always get the Use JsonReader.setLenient(true) to accept malformed JSON at path $. error. It looks like the quotes are getting stripped out of the JSON for some reason and that might be causing an issue. If I use my inner data class directly

Moshi and Retrofit2: Unable to read service response

I'm trying to read a json response from a webservice, but without success.
This is the json i receive:
{
"rsp": {
"#code": "0",
"#message": ""
},
"listOfStrings":[]
}
And this is relative data class where i parse response
data class Response(
val rsp : Rsp,
val listOfStrings : List<String>
)
data class Rsp(
#Json(name = "#code")
val code : String,
#Json(name = "#message")
val message : String
)
But it seems that moshi for some reason it's not able to parse json into object, because i always get Response object with all null fields.
So what's wrong? May the "#" character of json response fields cause problems?
UPDATE
Now i can parse correctly response by change #Json annotation into #field:Json:
data class Rsp(
#field:Json(name = "#code")
val code : String,
#field:Json(name = "#message")
val message : String
)
But i'm curious to know why it works.
#field:Json is required if you want moshi-kotlin to work with proguard according to the discussion here: https://github.com/square/moshi/issues/315
Try this model and let me know if it works:
#Parcelize
data class Response(
#Json(name = "rsp")
val rsp: Rsp,
#Json(name = "listOfStrings")
val listOfStrings: List<String>
) : Parcelable {
#Parcelize
data class Rsp(
#Json(name = "#code")
val code: String,
#Json(name = "#message")
val message: String
) : Parcelable
}
Edit:
If it didn't work, try to add back-slash behind those field names that have #.
Like: #Json(name = "\#code").
UPDATE AFTER QUESTION GOT UPDATE:
You need to add moshi-kotlin dependency and then using KotlinJsonAdapterFactory
val moshi = Moshi.Builder()
// ... add your own JsonAdapters and factories ...
.add(KotlinJsonAdapterFactory())
.build()
Then moshi couldn't ignore #Json.

How to use gson deserialize to ArrayList in Kotlin

I use this class to store data
public class Item(var name:String,
var description:String?=null){
}
And use it in ArrayList
public var itemList = ArrayList<Item>()
Use this code to serialize the object
val gs=Gson()
val itemListJsonString = gs.toJson(itemList)
And deserialize
itemList = gs.fromJson<ArrayList<Item>>(itemListJsonString, ArrayList::class.java)
But this method will give me LinkedTreeMap, not Item, I cannot cast LinkedTreeMap to Item
What is correct way to deserialize to json in Kotlin?
Try this code for deserialize list
val gson = Gson()
val itemType = object : TypeToken<List<Item>>() {}.type
itemList = gson.fromJson<List<Item>>(itemListJsonString, itemType)
You can define a inline reified extension function like:
internal inline fun <reified T> Gson.fromJson(json: String) =
fromJson<T>(json, object : TypeToken<T>() {}.type)
And use it like:
val itemList: List<Item> = gson.fromJson(itemListJsonString)
By default, types are erased at runtime, so Gson cannot know which kind of List it has to deserialize. However, when you declare the type as reified you preserve it at runtime. So now Gson has enough information to deserialize the List (or any other generic Object).
In my code I just use:
import com.google.gson.Gson
Gson().fromJson(string_var, Array<Item>::class.java).toList() as ArrayList<Type>
I give here a complete example.
First the type and the list array:
class Item(var name:String,
var description:String?=null)
var itemList = ArrayList<Item>()
The main code:
itemList.add( Item("Ball","round stuff"))
itemList.add(Item("Box","parallelepiped stuff"))
val striJSON = Gson().toJson(itemList) // To JSON
val backList = Gson().fromJson( // Back to another variable
striJSON, Array<Item>::class.java).toList() as ArrayList<Item>
val striJSONBack = Gson().toJson(backList) // To JSON again
if (striJSON==striJSONBack) println("***ok***")
The exit:
***OK***
Instead of the accepted answer (that works but creates an object to get its type), you could just do:
val gson = Gson()
itemList = gson.fromJson(itemListJsonString, Array<Item>::class.java)
There "Array" represents a Java array when targeting the JVM platform. That's not an ArrayList but you can access the items (that's usually all is needed after parsing JSON).
If you still need to manipulate the list you could easily convert it to mutable by doing:
itemsList.toMutableList()

Categories

Resources