I have this JSON in Kotlin, and I'm not able to fetch and parse. Any quick help. Please.
[{platform: {name: "mena-web",publishingRegion: "mena",platformGroup:"web",id: 2,countryCode: "AE",locales: {locale:["en_US","ar_AE"]}}}]
Here are my data classes:
data class Locales(var locale: ArrayList<String>) {}
data class Platform(var name: String, var publishingRegion: String, var platformGroup: String, var id: Int, var countryCode: String, var locales: Locales) {}
data class Json(var platform: Platform) {}
Here is my JSON API interface:
interface Api {
#GET("/me.json2")
fun getGeo(callback: Callback<List<Json>>): Call<List<Json>>
}
Here is my RestAPI:
class RestAPI(val api: Api) {
fun getNews(callback: Callback<List<Json>>) {
val call = api.getGeo(callback)
call.enqueue(callback)
}
}
Here is my RestAPI call:
try {
val api: RestAPI
val retrofit = Retrofit.Builder()
.baseUrl(PLATEFORM_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
api = retrofit.create(RestApi::class.java)
val callback = object : Callback<List<Json>> {
override fun onResponse(call: Call<List<Json>>?, response: retrofit2.Response<List<Json>>?) {
response?.isSuccessful.let {
this#MainActivity.photos = response?.body()
}
}
override fun onFailure(call: Call<List<Json>>?, t: Throwable?) {
Log.e("MainActivity", "Problems calling API", t)
}
}
api.getGeo(callback)
// Log.e("Message", test.getNews().toList().toString())
} catch(e:Exception){
Log.e("Message", e.message)
}
Thanks Guys!
I found the answer, everything stat working after changing the parser
MoshiConverterFactory to GsonConverterFactory.
respones is the string which will be your jsonResponse
try {
val rootArray = JSONArray(respones)
val mainObject=rootArray.getJSONObject(0)
val platformObject=mainObject.getJSONObject("platform")
val name=platformObject.getString("name")
val publishingRegion=platformObject.getString("publishingRegion")
val platformGroup=platformObject.getString("platformGroup")
val id=platformObject.getInt("id")
val countryCode=platformObject.getString("countryCode")
val localesObj=platformObject.getJSONObject("locales")
val localeArray=locales.getJSONArray("locale")
val stringOne=localeArray.getString(0)
val stringTwo=localeArray.getString(1)
} catch (e: JSONException){}
Related
I am currently implementing an API client with Ktor. The API I am requesting does not return a consistent JSON format.
for Example:
sometimes the JSON looks like this:
{
"description": {
"lang": "en",
"value": "an English description..."
},
...
}
and sometimes like this:
{
"description": [
{
"lang": "en",
"value": "an English description..."
},
{
"lang": "fr",
"value": "a French description..."
}
],
...
}
Now my Question:
How can I implement a Custom Kotlinx Deserializer to Decode an Object of T or a List<T> to a List<T>
My classes look like this:
#Serializable
class ResourceResponse(
#SerialName("description")
val descriptions: List<Description>
) {
#Serializable
data class Description(
#SerialName("value")
val value: String,
#SerialName("lang")
val language: String,
)
}
I want that a Json with only one Description-Object will be deserialized to a List with one Object and not specifically for the description, but in general for classes.
I've found nothing really helpful in the Web.
One solution is to first deserialize it to JsonElement, introspect and then decide how to deserialize it further into ResourceResponse:
fun decode(s: String): ResourceResponse {
val json = Json.parseToJsonElement(s).jsonObject
return when (val desc = json["description"]) {
is JsonArray -> Json.decodeFromJsonElement(json)
is JsonObject -> {
val json2 = json.toMutableMap()
json2["description"] = JsonArray(listOf(desc))
Json.decodeFromJsonElement(JsonObject(json2))
}
else -> throw IllegalArgumentException("Invalid value for \"description\": $desc")
}
}
This solution is definitely not ideal. It may be potentially less performant as we need to deserialize the whole tree into the tree of JsonElement objects only to transform it to the final types (although, maybe the library does this internally anyway). It works only for json and it is tricky to use this solution if ResourceResponse is somewhere deep into the data structure.
You can use a JsonContentPolymorphicSerializer to choose a deserializer based on the form of the JSON.
This one should work:
#Suppress("UNCHECKED_CAST")
class DescriptionsSerializer : JsonContentPolymorphicSerializer<List<ResourceResponse.Description>>(
List::class as KClass<List<ResourceResponse.Description>>
) {
// Here we check the form of the JSON we are decoding, and choose
// the serializer accordingly
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out List<ResourceResponse.Description>> {
return if (element is JsonArray)
ListSerializer(ResourceResponse.Description.serializer())
else
SingleDescriptionAsList()
}
class SingleDescriptionAsList : KSerializer<List<ResourceResponse.Description>> {
override val descriptor: SerialDescriptor
get() = ResourceResponse.Description.serializer().descriptor
override fun deserialize(decoder: Decoder): List<ResourceResponse.Description> {
return listOf(ResourceResponse.Description.serializer().deserialize(decoder))
}
override fun serialize(encoder: Encoder, value: List<ResourceResponse.Description>) {
throw Exception("Not in use")
}
}
}
You must also amend your original class to tell it to use this serializer:
#Serializable
class ResourceResponse(
#SerialName("description")
#Serializable(with = DescriptionsSerializer::class) val descriptions: List<Description>
) {
#Serializable
data class Description(
#SerialName("value")
val value: String,
#SerialName("lang")
val language: String,
)
}
Then you will be able to decode JSON objects with the single key "descriptions" using the ResourceResponse serializer.
For avoidance of doubt, if there are other keys in the JSON (it's not entirely clear from the question) then those should also be written into ResourceResponse definition.
After my research, I have now come up with a solution. For this you need a wrapper class. (here GenericResponse). I hope I can help others who have the same problem.
This is the Wrapper-Class
#Serializable(with = ListOrObjectSerializer::class)
class GenericResponse<T>(
val data: List<T> = emptyList()
) {
private var _isNothing : Boolean = false
val isNothing: Boolean
get() {
return this._isNothing
}
companion object {
fun <T> nothing(): GenericResponse<T> {
val o = GenericResponse(emptyList<T>())
o._isNothing = true
return o
}
}
}
And the Serializer looks like:
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
class ListOrObjectSerializer<T : Any>(private val tSerializer: KSerializer<T>): KSerializer<GenericResponse<T>> {
override val descriptor: SerialDescriptor
get() = tSerializer.descriptor
override fun deserialize(decoder: Decoder): GenericResponse<T> {
val input = decoder as JsonDecoder
val jsonObj = input.decodeJsonElement()
return when(jsonObj) {
is JsonObject -> GenericResponse(listOf(Json.decodeFromJsonElement(tSerializer, jsonObj)))
is JsonArray -> GenericResponse(Json.decodeFromJsonElement(ListSerializer(tSerializer), jsonObj))
else -> return GenericResponse.nothing()
}
}
override fun serialize(encoder: Encoder, value: GenericResponse<T>) {
throw IllegalAccessError("serialize not supported")
}
}
My Data-Class look now like:
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
#Serializable
class ResourceResponse(
#SerialName("description")
val descriptions: GenericResponse<Description>? = null,
) {
#Serializable
data class Description(
#SerialName("value")
val value: String? = null,
#SerialName("lang")
val language: String? = null,
)
}
data class ResourceResponse(
#SerializedName("description") val descriptions: List<Description>,
)
data class Description(
#SerializedName("value") val value: String,
#SerializedName("lang") val language: String,
)
it should be like that
I'm new in Kotlin as a PHP dev. I have a data model, something like this:
#Serializable
data class Site (
#SerialName("id")
val id: Int,
#SerialName("name")
val name: String,
#SerialName("accountId")
val accountId: Int,
}
I have JSON output something like the following, which comes from a external API and which I am unable to control:
{
"sites": {
"count": 1,
"site": [
{
"id": 12345,
"name": "Foobar",
"accountId": 123456
}
]
}
}
When trying to get this from the API with ktor HTTPClient, I'd like to instruct the serializer to use sites.site as the root for my Site datamodel. Currently, I get the error: Uncaught Kotlin exception: io.ktor.serialization.JsonConvertException: Illegal input and Caused by: kotlinx.serialization.json.internal.JsonDecodingException: Expected start of the array '[', but had 'EOF' instead at path: $
I'm using the following to fetch the endpoint:
package com.example.myapplication.myapp
import com.example.myapplication.myapp.models.Site
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
class Api {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
private val apiKey = "REDACTED"
private val installationId = "REDACTED"
private val apiHost = "REDACTED"
suspend fun getSitesList(): List<Site> {
return get("sites/list").body()
}
suspend fun get(endpoint: String): HttpResponse {
val response = client.get(buildEndpointUrl(endpoint))
return response
}
private fun buildEndpointUrl(endpoint: String): HttpRequestBuilder {
val builder = HttpRequestBuilder()
val parametersBuilder = ParametersBuilder()
parametersBuilder.append("api_key", apiKey)
builder.url {
protocol = URLProtocol.HTTPS
host = apiHost
encodedPath = endpoint
encodedParameters = parametersBuilder
}
builder.header("Accept", "application/json")
return builder
}
}
You have to model the whole response object and cannot just provide a model for some of its parts.
#Serializable
data class SitesResponse(
val sites: SitesContainer,
)
#Serializable
data class SitesContainer(
val count: Int,
val site: List<Site>,
)
#Serializable
data class Site(
val accountId: Int,
val id: Int,
val name: String,
)
you can try make your data model like this,
data class Site(
#SerializedName("sites")
var sites: Sites) {
data class Sites(
#SerializedName("count")
var count: Int,
#SerializedName("site")
var site: List<Site>
) {
data class Site(
#SerializedName("accountId")
var accountId: Int,
#SerializedName("id")
var id: Int,
#SerializedName("name")
var name: String
)
}}
I'm trying to build the right classes from the following URL:
https://api.nasa.gov/neo/rest/v1/feed?start_date=2020-01-01&end_date=2020-01-08&api_key=DEMO_KEY
At this moment, I have the following structure:
data class NearEarthObject (val asteroidObjects : Map<String, DateSelected>)
data class DateSelected (val date: ArrayList<Asteroid>) {
data class Asteroid(
val id: Long,
val codename: String,
val closeApproachDate: String,
val absoluteMagnitude: Double,
val estimatedDiameter: Double,
val relativeVelocity: Double,
val distanceFromEarth: Double,
val isPotentiallyHazardous: Boolean
)
And this is my Java code:
class MainActivity : AppCompatActivity() {
val URLAPI = Constants.BASE_URL
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
asteroidActivityRv.layoutManager = LinearLayoutManager(this)
asteroidActivityRv.adapter = null
val retrofitAsteroids = Retrofit.Builder()
.baseUrl(URLAPI)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiAsteroid = retrofitAsteroids.create(ApiAsteroids::class.java)
val callAsteroid = apiAsteroid.getAsteroids()
callAsteroid.enqueue(object : Callback<NearEarthObject> {
override fun onFailure(call: Call<NearEarthObject>?, t: Throwable?) {
Log.e("TAG fail", t.toString())
}
override fun onResponse(
call: Call<NearEarthObject>,
response: Response<NearEarthObject>
) {
for (res in response.body().asteroidObjects) {
// Log.e("TAG result", res.value.date)
}
}
})
}
}
But I keep receiving the following error:
java.lang.NullPointerException: Attempt to invoke interface method 'java.util.Set java.util.Map.entrySet()' on a null object reference
at com.example.nasanwsproject.MainActivity$onCreate$1.onResponse(MainActivity.kt:44)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:68)
Does anybody know what I'm doing wrong?
Thanks a lot for your help!
it looks response.body().asteroidObjects==null
i think this problem because you cant receive the response use map。
it require a definite class(include the field and value。)
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
fun main() {
val jsonString: String = """{
"jsonrpc": "2.0",
"id": null,
"result": [
{
"id": 1,
"name": "Lekhnath Rijal"
},
{
"id": 2,
"name": "Administrator"
}
]
}"""
val body1 = Gson().fromJson<RpcResult<List<Partner>>>(jsonString, object: TypeToken<RpcResult<List<Partner>>>(){}.type)
println(body1.result[0].name) // prints Lekhnath Rijal // - As expected
val body2 = fromJson<RpcResult<List<Partner>>>(jsonString)
println(body2.result[0].name) // throws Exception as stated below after this code snippet
}
fun <T> fromJson(json: String?): T {
return Gson().fromJson<T>(json, object: TypeToken<T>(){}.type)
}
data class RpcResult<T>(
val jsonrpc: String,
val id: Int?,
val result: T
)
data class Partner(
val id: Int,
val name: String
)
Exception: java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to class RpcResult
while converting json string to data class object without using function it works as expected but executing same code from helper function does not work and instead throws an exception mentioned above. What am I missing here?
It is due to type erasure in runtime. In Kotlin you can solve this issue by making your function inline with reified type:
Change your function from:
fun <T> fromJson(json: String?): T {
return Gson().fromJson<T>(json, object: TypeToken<T>(){}.type)
}
To:
inline fun <reified T> fromJson(json: String?): T {
return Gson().fromJson<T>(json, object: TypeToken<T>(){}.type)
}
For further reading check this out: https://kotlinlang.org/docs/reference/inline-functions.html
I'm creating my first application with AKKA-http. I'm currently using spray-json to write object to json. When I created GET request everything is working fine, but I tried a POST request and the following error shows:
Error:(55, 26) could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[nl.quintor.model.Employee]
entity(as[Employee]) { request =>
This is my main class:
object Main extends App with Routes {
implicit val system = ActorSystem("system")
implicit val executor: ExecutionContext = system.dispatcher
implicit val materializer: ActorMaterializer = ActorMaterializer()
override val slickboard = createSlickboard
Http().bindAndHandle(routes, httpInterface, httpPort)
private def createSlickboard: ActorRef =
system.actorOf(Slickboard.props(system), applicationName)
}
}
I defined my routes in the following trait:
trait Routes extends CORSSupport with Protocols {
val slickboard: ActorRef
implicit val timeout = Timeout(5 seconds)
val routes =
corsHandler {
pathPrefix("employees") {
pathEnd {
get {
complete {
(slickboard ? GetAllEmployees).mapTo[HttpResponse]
}
}
}
} ~
pathPrefix("employee") {
path(IntNumber) { id =>
get {
complete {
(slickboard ? GetEmployeeById(id)).mapTo[HttpResponse]
}
} ~
post {
decodeRequest {
entity(as[Employee]) { request =>
complete {
request.toString()
}
}
}
}
}
}
}
}
I defined my protocol just like spray recommended:
trait Protocols extends DefaultJsonProtocol {
implicit val employeeFormat = jsonFormat8(Employee.apply)
}
The class I'm trying to convert to json is the following:
case class Employee(ID: Int, firstname: String, lastname: String, street: String, zipCode: String, city: String, phoneNumber: String, image: String)
I tried multiple solutions found in stack overflow, but none of them are actually working.
Could someone please help me with this problem?
I suspect you need
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._