I'm trying to implement a protocol where (part of it) is sending a list of small images over a socket. I'm using JSON and the images are base64 encoded.
Here's the data classes
#Serializable
sealed class CmdBase {
abstract val cmd: Command
}
#Serializable
#SerialName("CmdIdImgs")
class CmdIdImgs(
override val cmd: Command,
val id: String,
#Serializable(with = ImageListSerializer::class)
val thumbnails: List<BufferedImage>) : CmdBase()
So I added a serializer for BufferedImage
object ImageSerializer: KSerializer<BufferedImage> {
override val descriptor = PrimitiveSerialDescriptor("Image.image", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): BufferedImage {
val b64str = decoder.decodeString()
return ImageIO.read(ByteArrayInputStream(Base64.getDecoder().decode(b64str)))
}
override fun serialize(encoder: Encoder, value: BufferedImage) {
val buff = ByteArrayOutputStream()
ImageIO.write(value, "PNG", buff)
val b64str = Base64.getEncoder().encodeToString(buff.toByteArray())
encoder.encodeString(b64str)
}
}
But it's a list of BufferedImages, so I added a serializer for that
class ImageListSerializer: KSerializer<List<BufferedImage>> {
private val listSerializer = ListSerializer(ImageSerializer)
override val descriptor: SerialDescriptor = listSerializer.descriptor
override fun serialize(encoder: Encoder, value: List<BufferedImage>) {
listSerializer.serialize(encoder, value)
}
override fun deserialize(decoder: Decoder): List<BufferedImage> = with(decoder as JsonDecoder) {
decodeJsonElement().jsonArray.mapNotNull {
try {
json.decodeFromJsonElement(ImageSerializer, it)
} catch (e: SerializationException) {
e.printStackTrace()
null
}
}
}
}
And now a serializer for the whole class
object CmdIdImgsSerializer : SerializationStrategy<CmdIdImgs>, DeserializationStrategy<CmdIdImgs> {
override val descriptor = buildClassSerialDescriptor("CmdIdImgs") {
element("cmd", Command.serializer().descriptor)
element("id", String.serializer().descriptor)
element("thumbnails", ImageListSerializer().descriptor)
}
override fun serialize(encoder: Encoder, value: CmdIdImgs) {
encoder.encodeStructure(descriptor) {
encodeSerializableElement(descriptor, 0, Command.serializer(), value.cmd)
encodeSerializableElement(descriptor, 1, String.serializer(), value.id)
encodeSerializableElement(descriptor, 2, ImageListSerializer(), value.thumbnails)
}
}
override fun deserialize(decoder: Decoder): CmdIdImgs =
decoder.decodeStructure(descriptor) {
var cmd: Command = Command.FULL_TREE
var id: String = ""
var thumbnails: List<BufferedImage> = listOf()
loop# while (true) {
when (val i = decodeElementIndex(descriptor)) {
0 -> cmd = decodeSerializableElement(descriptor, i, Command.serializer())
1 -> id = decodeSerializableElement(descriptor, i, String.serializer())
2 -> thumbnails = decodeSerializableElement(descriptor, i, ImageListSerializer())
CompositeDecoder.DECODE_DONE -> break
else -> throw SerializationException("Unknown index $i")
}
}
CmdIdImgs(cmd, id, thumbnails)
}
}
But something is wrong, because I still get
Serializer has not been found for type 'BufferedImage'
on the 'val thumbnails: List<BufferedImage>' in the CmdIdImgs class
Any idea what I'm doing wrong?
Probably a lot since I'm a newbie with Kotlin :-)
Since you want to send JSON to your socket, I recommend you use de facto Jackson. If that's ok for you, then this is simpler - you only need to create one specialised serializer. Here's working code (deserializer TODO).
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.module.kotlin.KotlinModule
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.File
import java.util.*
import javax.imageio.ImageIO
sealed class CmdBase {
abstract val cmd: String // Command
}
class CmdIdImgs(
override val cmd: String, // Command
val id: String,
val thumbnails: List<BufferedImage>,
) : CmdBase()
class BufferedImageSerializer : StdSerializer<BufferedImage>(BufferedImage::class.java) {
override fun serialize(value: BufferedImage?, jgen: JsonGenerator, provider: SerializerProvider?) {
value?.let {
val buff = ByteArrayOutputStream()
ImageIO.write(it, "PNG", buff)
val b64str = Base64.getEncoder().encodeToString(buff.toByteArray())
jgen.writeString(b64str)
}
}
}
//class BufferedImageDeserializer : StdDeserializer<BufferedImage>(BufferedImage::class.java) {
// override fun deserialize(jp: JsonParser, ctxt: DeserializationContext?): BufferedImage? {
// val node: JsonNode = jp.codec.readTree(jp)
// if (!node.isTextual) {
// node.asText()....
// }
// }
//}
val IMAGE_MODULE = SimpleModule().apply {
this.addSerializer(BufferedImage::class.java, BufferedImageSerializer())
//this.addDeserializer(BufferedImage::class.java, BufferedImageDeserializer())
}
val MAPPER = JsonMapper.builder()
.addModule(KotlinModule(strictNullChecks = true))
.addModule(IMAGE_MODULE)
.build()
fun main(args: Array<String>) {
val cmdIdImgs = CmdIdImgs("x", "1", listOf(ImageIO.read(File("/tmp/image.png"))))
println(MAPPER.writeValueAsString(cmdIdImgs))
}
Prints
{"cmd":"x","id":"1","thumbnails":["iVBORw0KGgoAAAAN....
Related
I am new to Kotlin and Android Studio and I am tasked with creating a tab layout with 3 tabs and with view pager and fragments. Each fragment will have a list of songs from different genres: Rock, Pop, and Classic. I am given APIs and I created json files from these APIs. I created the Xmls and setup the layouts, adapters, data classes, and everything. Now I'm trying to get the data from the json file into the fragment using recycler view and card view. Here is the error I am getting:
Type mismatch: inferred type is MainActivity but List was expected and Classifier 'MyAdapter' does not have a companion object, and thus must be initialized here. Here is my code:
//Main Activity
package com.example.itunes_mysia
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.nio.charset.Charset
var artist_name: ArrayList<String> = ArrayList()
var track_name: ArrayList<String> = ArrayList()
var track_price: ArrayList<String> = ArrayList()
class MainActivity : AppCompatActivity() {
private lateinit var itunesToolbar: androidx.appcompat.widget.Toolbar
private lateinit var itunesTabs: TabLayout
private lateinit var itunesTitleText: TextView
private lateinit var itunesViewPager: ViewPager
private lateinit var itunesPagerAdapters: PagerAdapters
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "KotlinApp"
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
val linearLayoutManager = LinearLayoutManager(applicationContext)
recyclerView.layoutManager = linearLayoutManager
try {
val obj = JSONObject(loadJSONFromAsset())
val rockArray = obj.getJSONArray("Rock")
for (i in 0 until rockArray.length()) {
val userDetail = rockArray.getJSONObject(i)
artist_name.add(userDetail.getString("Artist"))
track_name.add(userDetail.getString("Track Name"))
track_price.add(userDetail.getString("Track Price"))
}
}
catch (e: JSONException) {
e.printStackTrace()
}
val myAdapter = MyAdapter(this#MainActivity)
recyclerView.adapter = MyAdapter
// Set find ID
itunesToolbar = findViewById(R.id.itunesToolbar)
itunesTitleText = findViewById(R.id.itunesTitleText)
itunesTabs = findViewById(R.id.itunesTabs)
itunesViewPager = findViewById(R.id.itunesViewPager)
itunesPagerAdapters = PagerAdapters(supportFragmentManager)
// Set Toolbar
itunesToolbar.setTitle("")
itunesTitleText.setText(getString(R.string.itunes))
setSupportActionBar(findViewById(R.id.itunesToolbar))
// Set Fragment List
itunesPagerAdapters.addfragment(RockFragment(), "Rock")
itunesPagerAdapters.addfragment(ClassicFFragment(), "Classic")
itunesPagerAdapters.addfragment(PopFragment(), "Pop")
// Set View Pager Adapter
itunesViewPager.adapter = itunesPagerAdapters
// Set Tab Layout with View Pager Adapter
itunesTabs.setupWithViewPager(itunesViewPager)
// Set Icons
itunesTabs.getTabAt(0)!!.setIcon(R.mipmap.music1)
itunesTabs.getTabAt(1)!!.setIcon(R.mipmap.music2)
itunesTabs.getTabAt(2)!!.setIcon(R.mipmap.music3)
}
class ViewPagerOnTabSelectedListener(private val viewPager: ViewPager) :
OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
viewPager.currentItem = tab.position
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
// No-op
}
override fun onTabReselected(tab: TabLayout.Tab?) {
// No-op
}
}
private fun loadJSONFromAsset(): String {
val json: String?
try {
val inputStream = assets.open("rock.json")
val size = inputStream.available()
val buffer = ByteArray(size)
val charset: Charset = Charsets.UTF_8
inputStream.read(buffer)
inputStream.close()
json = String(buffer, charset)
}
catch (ex: IOException) {
ex.printStackTrace()
return ""
}
return json
}
}
//MyAdapter.kt (for recycler view)
package com.example.itunes_mysia
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class MyAdapter(private val Rock: List<Rock>) :
RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
MyViewHolder {
val itemView= LayoutInflater.from(parent.context).inflate(R.layout.row, parent,
false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = Rock[position]
holder.artistName.text = currentItem.artistName
holder.trackName.text = currentItem.trackName
holder.trackPrice.text = currentItem.trackPrice
}
override fun getItemCount(): Int {
return Rock.size
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val artistName: TextView = itemView.findViewById(R.id.art_name)
val trackName: TextView = itemView.findViewById(R.id.trackName)
val trackPrice: TextView = itemView.findViewById(R.id.trackPrice)
}
}
As part of one of my courses, I am using an API in Android Studio for the first time. For this I use OkHttp. However, I have the following error when I launch my application. I guess it's because the JSON is not parsed well but I can't find a solution.
If anyone could help me that would be great!
Thank you
The error screenshot
MyMarqueRecyclerViewAdapter.kt
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.google.gson.GsonBuilder
import kotlinx.android.synthetic.main.fragment_marque_list.*
import okhttp3.*
import java.io.IOException
/**
* A fragment representing a list of Items.
*/
class MarqueFragment : Fragment(), OnMarqueClickListener {
private var columnCount = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
columnCount = it.getInt(ARG_COLUMN_COUNT)
}
fetchJson()
}
fun fetchJson() {
val url = "https://tp3.infomobile.app/api/v1/brand"
val request = Request.Builder().url(url).build()
val lesmarques = OkHttpClient()
lesmarques.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
println(body)
val gson = GsonBuilder().create()
val homeFeed = gson.fromJson(body, HomeFeed::class.java)
println(homeFeed)
activity?.runOnUiThread {
recyclerView_main.adapter = MyMarqueRecyclerViewAdapter(homeFeed)
}
}
override fun onFailure(call: Call, e: IOException) {
println("Failed to execute request")
}
})
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_marque_list, container, false)
// Set the adapter
if (view is RecyclerView) {
with(view) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
//adapter = MyMarqueRecyclerViewAdapter(homeFeed)
}
}
return view
}
override fun onMarqueItemClicked(position: Int) {
Toast.makeText(this.context, "ça marche", Toast.LENGTH_LONG).show()
val intent = Intent(this#MarqueFragment.requireContext(),MainActivity2::class.java)
startActivity(intent)
}
companion object {
// TODO: Customize parameter argument names
const val ARG_COLUMN_COUNT = "column-count"
// TODO: Customize parameter initialization
#JvmStatic
fun newInstance(columnCount: Int) =
MarqueFragment().apply {
arguments = Bundle().apply {
putInt(ARG_COLUMN_COUNT, columnCount)
}
}
}
}
class HomeFeed(val marques: List<Marque>)
class Marque(val id: Int, val name: String)
MarqueFragment.kt
import android.content.Intent
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import ca.ulaval.ima.tp3.placeholder.PlaceholderContent.PlaceholderItem
import ca.ulaval.ima.tp3.databinding.FragmentMarqueBinding
/**
* [RecyclerView.Adapter] that can display a [PlaceholderItem].
* TODO: Replace the implementation with code for your data type.
*/
class MyMarqueRecyclerViewAdapter(val homeFeed: HomeFeed?) : RecyclerView.Adapter<MyMarqueRecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
FragmentMarqueBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = homeFeed?.marques?.get(position)
holder.contentView.text = item.toString()
holder.contentView.setOnClickListener{
val context=holder.contentView.context
val intent = Intent( context, MainActivity2::class.java)
intent.putExtra("marque", item.toString())
context.startActivity(intent)
}
}
override fun getItemCount(): Int {
return homeFeed?.marques!!?.count()
}
inner class ViewHolder(binding: FragmentMarqueBinding) : RecyclerView.ViewHolder(binding.root) {
val contentView: TextView = binding.content
override fun toString(): String {
return super.toString() + " '" + contentView.text + "'"
}
}
}
First of all, you can trace the crash using the log which clearly states that the crash happened at line 42 in MyMarqueRecyclerViewAdapter.kt, in the getItemCount function, which means that this functions is causing it:
override fun getItemCount(): Int {
return homeFeed?.marques!!?.count()
}
What's happening here is that you're forcing kotlin to treat a property that might be null as a non null property, by using the "!!" operator, this tells kotlin that this propery would never be null in any case, and that isn't true in your case since its throwing a NullPointerException, what you actually need to do is allow it to be null and provide an alternative value in case it was null, using the elvis operator, like this:
override fun getItemCount(): Int {
return homeFeed?.marques?.count() ?: 0
}
Another thing is that you're using the count() function, i think you meant to use the size property instead:
override fun getItemCount(): Int {
return homeFeed?.marques?.size ?: 0
}
Second, i noticed your class declaration and compared the content to the json's response content, it seems that the json response conatins a parameter called "content", while the propery in the HomeFeed class is called "marques", which results in GSON not knowing where to get "marques" from, it only knows that there is a parameter called "content" in the json and it doesn't know what to do with it, the best solution would be to annotate the HomeFeed.marques property with a #SerializedName annotation, and provide the corresponding json parameter that should be mapped to this property, like so:
class HomeFeed(
#SerializedName("content")
val marques: List<Marque>
)
class Marque(
#SerializedName("id")
val id: Int,
#SerializedName("name")
val name: String
)
Hope this helps!
Some resources:
Null Safety | Kotlin
Difference between list.count() and list.size
Parsing between Kotlin classes and Json objects using GSON
When deserialize json to Map<out Any, Any>, gson will use Double to fill the Map, even the field is int number, so I use a MapDeserializerDoubleAsIntFix to covert number to int if it is possible.
{
"person":{
"name":"jack",
"age":24,
"height":174.5
}
}
class MapDeserializerDoubleAsIntFix: JsonDeserializer<Map<out Any, Any>> {
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): Map<out Any, Any>? {
return deserialize(json) as Map<out Any, Any>
}
private fun deserialize(jsonElement: JsonElement): Any? {
when {
jsonElement.isJsonArray -> {
val list: MutableList<Any?> = ArrayList()
val arr = jsonElement.asJsonArray
for (anArr in arr) {
list.add(deserialize(anArr))
}
return list
}
jsonElement.isJsonObject -> {
val map: MutableMap<String, Any?> = LinkedTreeMap()
val obj = jsonElement.asJsonObject
val entitySet = obj.entrySet()
for ((key, value) in entitySet) {
map[key] = deserialize(value)
}
return map
}
jsonElement.isJsonPrimitive -> {
val prim = jsonElement.asJsonPrimitive
when {
prim.isBoolean -> {
return prim.asBoolean
}
prim.isString -> {
return prim.asString
}
prim.isNumber -> {
// Here is what i do
// use int or long if it is possible
val numStr = prim.asString
return if (numStr.contains(".")) {
prim.asDouble
} else {
val num = prim.asNumber
val numLong = num.toLong()
return if (numLong < Int.MAX_VALUE && numLong > Int.MIN_VALUE) {
numLong.toInt()
} else {
numLong
}
}
}
}
}
}
return null
}
}
object MapTypeToken: TypeToken<Map<out Any, Any>>()
private val GSON = GsonBuilder()
.registerTypeAdapter(MapTypeToken.type, MapDeserializerDoubleAsIntFix())
.create()
And when I use GSON deserialize json as map, it works.
val map: Map<out Any, Any> = GSON.fromJson(json, MapTypeToken.type)
But when the map is in a data class as a field, the MapDeserializerDoubleAsIntFix not work.
data class Test(
val person: Map<out Any, Any>
)
val test = GSON.fromJson(json, Test::class.java)
So is there any way to deserialize map or filed map ?
Now I use this to solve.
class TestJsonDeserializer: JsonDeserializer<Test> {
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext,
): Test {
val jsonObject = json.asJsonObject
val personElement = jsonObject.get("person")
val person = context.deserialize<Map<out Any, Any>>(personElement, MapTypeToken.type)
return Test(person)
}
}
private val GSON = GsonBuilder()
.registerTypeAdapter(MapTypeToken.type, MapDeserializerDoubleAsIntFix())
.registerTypeAdapter(Test::class.java, TestJsonDeserializer())
.create()
But if another data class has map, I have to write another JsonDeserializer and register it.
Hope there is a better way to make the MapDeserializerDoubleAsIntFix work as global.
I'm struggling with asynchronous calls - the app is just crashing. I want to load a JSON-file (containing 100 JSON-objects) from an URL and then send it to RecyclerView.
Here is the MainActivity-class:
class MainActivity : AppCompatActivity() {
lateinit var recyclerView: RecyclerView
lateinit var linearLayoutManager: LinearLayoutManager
private val url = [//some address here]
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = linearLayoutManager
AsyncTaskHandler().execute(url)
}
inner class AsyncTaskHandler : AsyncTask<String, String, String>() {
override fun onPreExecute() {
super.onPreExecute()
}
override fun doInBackground(vararg url: String?): String {
val text: String
val connection = URL(url[0]).openConnection() as HttpURLConnection
try {
connection.connect()
text = connection.inputStream.use { it.reader().use {reader -> reader.readText()} }
} finally {
connection.disconnect()
}
return text
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
handleJson(result)
}
}
private fun handleJson(jsonString: String?) {
val jsonArray = JSONArray(jsonString)
var list = mutableListOf<DataSet>()
var i = 0
while (i < jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
list.add(DataSet(
jsonObject.getString("title"),
jsonObject.getString("type")
))
i++
}
val adapter = Adapter(list)
recyclerView.adapter = adapter
}
}
...and ListAdapter-class:
class Adapter (private var targetData: MutableList<DataSet>): RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.element, parent, false)
return ViewHolder(v);
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = targetData[position]
holder.title?.text = item.title
holder.type?.text = item.type
}
override fun getItemCount(): Int {
return targetData.size
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var title = itemView.findViewById<TextView>(R.id.itemTitle)
var type = itemView.findViewById<TextView>(R.id.itemType)
}
What might be the problem here? Is there any better option to perform this?
I have api that return json:
{"countries":[{"id":1,"name":"Australia"},{"id":2,"name":"Austria"}, ... ]}
I write model class (Kotlin lang)
data class Country(val id: Int, val name: String)
And I want do request using retorift that returning List < Models.Country >, from "countries" field in json
I write next:
interface DictService {
#GET("/json/countries")
public fun countries(): Observable<List<Models.Country>>
companion object {
fun create() : DictService {
val gsonBuilder = GsonBuilder()
val listType = object : TypeToken<List<Models.Country>>(){}.type
gsonBuilder.registerTypeAdapter(listType, CountriesDeserializer)
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
val service = Retrofit.Builder()
.baseUrl("...")
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
.build()
return service.create(DictService::class.java)
}
}
object CountriesDeserializer : JsonDeserializer<List<Models.Country>> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): List<Models.Country>? {
val res = ArrayList<Models.Country>()
if(json!=null) {
val countries = json.asJsonObject.get("countries")
if (countries.isJsonArray()) {
for (elem: JsonElement in countries.asJsonArray) {
res.add(Gson().fromJson(elem, Models.Country::class.java))
}
}
}
return null;
}
}
}
But I get error:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
CountriesDeserializer code dont execute even!
What they want from me?
Maybe I need write my own TypeAdapterFactory?
I dont want use model class like
class Countries {
public List<Country> countries;
}
If your intention is to simplify the interface and hide the intermediate wrapper object I guess the simplest thing to do is to add an extension method to the DictService like so:
interface DictService {
#GET("/json/countries")
fun _countries(): Observable<Countries>
}
fun DictService.countries() = _countries().map { it.countries }
data class Countries(val countries: List<Country> = listOf())
Which can then be used as follows:
val countries:Observable<List<Country>> = dictService.countries()
I found the way:
object CountriesTypeFactory : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson?, type: TypeToken<T>?): TypeAdapter<T>? {
val delegate = gson?.getDelegateAdapter(this, type)
val elementAdapter = gson?.getAdapter(JsonElement::class.java)
return object : TypeAdapter<T>() {
#Throws(IOException::class)
override fun write(outjs: JsonWriter, value: T) {
delegate?.write(outjs, value)
}
#Throws(IOException::class)
override fun read(injs: JsonReader): T {
var jsonElement = elementAdapter!!.read(injs)
if (jsonElement.isJsonObject) {
val jsonObject = jsonElement.asJsonObject
if (jsonObject.has("countries") && jsonObject.get("countries").isJsonArray) {
jsonElement = jsonObject.get("countries")
}
}
return delegate!!.fromJsonTree(jsonElement)
}
}.nullSafe()
}
}
But it is very complex decision, I think, for such problem.
Are there another one simpler way?
Another one:
I found bug in my initial code from start meassage!!!
It works fine if replace List by ArrayList!
I would use Jackson for this task.
Try this https://github.com/FasterXML/jackson-module-kotlin
val mapper = jacksonObjectMapper()
data class Country(val id: Int, val name: String)
// USAGE:
val country = mapper.readValue<Country>(jsonString)