Dynamic unknow field in JSON - json

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。)

Related

Kotlinx Deserialization Object or List of Object to List of Object

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

Skipping serialization of null values depending on the type

I started working with moshi a couple of weeks ago, so maybe I am missing something trivial, but I spent already quite a bit of time trying to fix this without success, so here is my question.
Having the following reproducible code:
fun main() {
val moshi = Moshi.Builder().add(OptionalAdapter).build()
val objectToSerialize = DummyObject()
val json = moshi.adapter(DummyObject::class.java).serializeNulls().toJson(objectToSerialize)
println(json)
}
#JsonClass(generateAdapter = true)
data class DummyObject(val value: Int=123, val someNullable: String? = null,
val someNotPresent: Optional<String> = Optional.NotPresent,
val somePresent: Optional<String> = Optional.Present("aaaa"))
class OptionalAdapter<T>(private val valueAdapter: JsonAdapter<T>) : JsonAdapter<Optional<T>>() {
#Suppress("UNCHECKED_CAST")
override fun fromJson(reader: JsonReader) = Optional.Present(valueAdapter.fromJson(reader) as T)
override fun toJson(writer: JsonWriter, value: Optional<T>?) {
when (value) {
is Optional.NotPresent -> writer.nullValue()
is Optional.Present -> valueAdapter.serializeNulls().toJson(writer, value.value)
}
}
companion object Factory : JsonAdapter.Factory {
override fun create(type: Type, annotations: Set<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
return if (Types.getRawType(type) == Optional::class.java && annotations.isEmpty()) {
val valueType = if(type is ParameterizedType) {
type.actualTypeArguments.get(0)
} else {
//Should not happen
throw IllegalArgumentException()
}
return OptionalAdapter(moshi.adapter<Any>(valueType).nullSafe())
} else {
null
}
}
}
}
sealed class Optional<out T> {
val provided get() = this !is NotPresent
abstract val value: T
object NotPresent : Optional<Nothing>() {
// have the IDE raise an error if the user knows a type is missing but still tries to access a value
#Deprecated(
"Cannot access a missing value",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("TODO(\"value is missing\")")
)
override val value: Nothing
get() = error("cannot access provided field")
}
data class Present<out T>(override val value: T) : Optional<T>()
}
I would like to serialize as {"value":123,"someNullable":null,"somePresent":"aaaa"} instead of {"value":123,"someNullable":null,"someNotPresent":null,"somePresent":"aaaa"}, which is what is doing now.
Basically, I want to skip the serialization in case the type is Optional.NotPresent. Any suggestion?
The solution I ended up with:
override fun toJson(writer: JsonWriter, value: Optional<T>?) {
when (value) {
is Optional.NotPresent -> {
val wasSerializeNulls = writer.serializeNulls
writer.serializeNulls = false
try {
writer.nullValue()
} finally {
writer.serializeNulls = wasSerializeNulls
}
}
is Optional.Present -> valueAdapter.serializeNulls().toJson(writer, value.value)
}
}

class com.google.gson.internal.LinkedTreeMap cannot be cast to class Partner

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

Kotlin Json Parser

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

No ByteString deserializer found for type models.MpMember. Try to implement an implicit ByteStringDeserializer for this type

I am using etaty rediscala (1.4.2) to connect to Redis in Play 2.4. My code is below:
override def getMember(token: String): Future[Option[Member]] = {
redisClient.get[Member](token)
}
However, It shows this error:
No ByteString deserializer found for type models.Member. Try to implement an implicit ByteStringDeserializer for this type.
My Member is as follows:
case class Member(
memberId : Long = 0l,
email : String = "",
firstName : Option[String] = None,
lastName : Option[String] = None
)
object Member {
implicit val memberReads : Reads[Member] = Json.reads[Member]
implicit val memberWrites : Writes[Member] = Json.writes[Member]
}
Thank you for your helps.
I found the solution. I convert the Scala object to Json and save to Redis as a string.
object Member {
implicit val byteStringFormatter = new ByteStringFormatter[Member] {
def serialize(data: Member): ByteString = {
ByteString(Json.toJson(data).toString)
}
def deserialize(bs: ByteString): Member = {
val s = bs.utf8String
Json.fromJson[Member](Json.parse(s)).get
}
}
}