How to parse LinkedHashMap in moshi (kotlin) - kotlin

I am trying to create a JSON adapter for the following json
{
"message": {
"affenpinscher": [],
"african": [],
"airedale": [],
"akita": [],
"appenzeller": [],
"australian": [
"shepherd"
]
},
"status": "success"
}
I have tried the following
#JsonClass(generateAdapter = true)
data class BreedList(
val message: HashMap<String,List<String>> = HashMap<String,List<String>>()
)
and
#JsonClass(generateAdapter = true)
data class BreedList(
val message: Breed
)
#JsonClass(generateAdapter = true)
data class Breed(
val breed: List<String>
)
But both scenarios give me the errors, is there a way to parse the following object, I need the key as well as the list from the response

There is no need to create a custom adapter.
To parse the JSON you posted:
data class Base (
#field:Json(name = "message")
val message : Message,
#field:Json(name = "status")
val status : String
)
data class Message (
#field:Json(name = "affenpinscher")
val affenpinscher : List<String>,
#field:Json(name = "african")
val african : List<String>,
#field:Json(name = "airedale")
val airedale : List<String>,
#field:Json(name = "akita")
val akita : List<String>,
#field:Json(name = "appenzeller")
val appenzeller : List<String>,
#field:Json(name = "australian")
val australian : List<String>
)
Note: instead of String you can use whatever data type you need or create custom classes like Message.

Related

Jackon JSON Adding deserializer module doesn't work, but annotation on class does

I am trying to implement a custom deserializer and when I try to install it on the ObjectMapper it never gets invoked, but when I use it directly as an annotation on the class, it does. Can someone explain why and how I can actually install it on the object mapper itself?
This is never invoked
val bug = ObjectMapper().registerModule(KotlinModule.Builder().build())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(SimpleModule().addDeserializer(Bug::class.java, BugDeserializer()))
.readValue(bugStream, Bug::class.java)
data class Bug(
#JsonProperty("rptno")
val id: Long,
#JsonProperty("status")
#JsonDeserialize(using = StatusDeserializer::class)
val status: Status,
#JsonProperty("reported_date")
val reportedDate:Instant,
#JsonProperty("updated_date")
val updatedDate: Instant,
// val pillar: String = "",
#JsonProperty("product_id")
#JsonDeserialize(using = ProductDeserializer::class)
val product: Product,
#JsonProperty("assignee")
val assignee: String,
// val serviceRequests: List<Long> = listOf(),
#JsonProperty("subject")
val title: String,
#JsonProperty("bug_type")
val type: String
)
But, this does:
val bug = ObjectMapper().registerModule(KotlinModule.Builder().build())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(bugStream, Bug::class.java)
#JsonDeserialize(using = BugDeserializer::class)
data class Bug(
#JsonProperty("rptno")
val id: Long,
#JsonProperty("status")
#JsonDeserialize(using = StatusDeserializer::class)
val status: Status,
#JsonProperty("reported_date")
val reportedDate:Instant,
#JsonProperty("updated_date")
val updatedDate: Instant,
// val pillar: String = "",
#JsonProperty("product_id")
#JsonDeserialize(using = ProductDeserializer::class)
val product: Product,
#JsonProperty("assignee")
val assignee: String,
// val serviceRequests: List<Long> = listOf(),
#JsonProperty("subject")
val title: String,
#JsonProperty("bug_type")
val type: String
)
```kotlin

Moshi: Expected BEGIN_ARRAY but was BEGIN_OBJECT at path

I get the following error from Moshi: Expected BEGIN_ARRAY but was BEGIN_OBJECT at path
This is my interface:
interface ApiService {
#GET("movie/popular")
suspend fun getTopRatedMovies(
#Query("api_key") apiKey: String = BuildConfig.API_KEY,
#Query("page") page: Int = 1
): List<TopRatedMovies>
data class
data class TopRatedMovies(
#Json(name = "title") val title: String,
#Json(name = "poster_path") val posterPath: String,
)
The response looks like this:
I know that there are some other questions with the same title but those didn't help me.
From the function's return type (List<TopRatedMovies>), Moshi is expecting your API to return a list, but it's returning an object ({"page": ..., "results": [...]}) instead.
To handle this, you can create a TopRatedMoviesPage class like this:
data class TopRatedMoviesPage(
#Json(name = "page") val page: Int,
#Json(name = "results") val results: List<TopRatedMovies>,
)
And change your API definition to this:
#GET("movie/popular")
suspend fun getTopRatedMovies(
#Query("api_key") apiKey: String = BuildConfig.API_KEY,
#Query("page") page: Int = 1
): TopRatedMoviesPage

How to restrict Int values bound to database column?

data class:
// Entity of query
#Entity(tableName = TABLE_NAME)
data class HistoryItem(
#PrimaryKey(autoGenerate = true)
val id: Int,
#ColumnInfo(name = SEARCHED_DOMAIN)
val searchedDomain: String,
#ColumnInfo(name = STATUS)
val status: Int,
)
And object of statuses:
object Statuses {
const val FAILURE = 0
const val NOT_FOUND = 1
const val FOUND = 2
}
How to make val status: Int to always be FAILURE or NOT_FOUND or FOUND? I think it should looks like this:
#Status
#ColumnInfo(name = STATUS)
val status: Int
But how to do it?
I would recommend using an enum class for this:
enum class Status {
FAILURE, NOT_FOUND, FOUND;
}
#Entity(tableName = TABLE_NAME)
data class HistoryItem(
#PrimaryKey(autoGenerate = true)
val id: Int,
#ColumnInfo(name = SEARCHED_DOMAIN)
val searchedDomain: String,
#ColumnInfo(name = STATUS)
val status: Status
)
However, older versions of Android Room (prior to 2.3.0) do not automatically convert enum classes, so if you're using these you will need to use a type convertor:
class Converters {
#TypeConverter
fun toStatus(value: Int) = enumValues<Status>()[value]
#TypeConverter
fun fromStatus(value: Status) = value.ordinal
}
Which requires you to add the following to your database definition:
#TypeConverters(Converters::class)
See also this answer.

Having trouble with deserialising JSON nullable fields in Kotlin with custom decoder

I am having difficulty decoding this JSON with Kotlin Serialization. It works well when the data field is not empty. However when the data field is null and the errors field is present I get this runtime exception:
kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 7: Expected start of the object '{', but had ':' instead
JSON input: {"data":null,"errors":[{"path":null,"locations":[{"line":3,"column":5,"sourceName":null}],"message":"Validation error of type FieldUndefined: Field 'mee' in type 'Query' is undefined # 'mee'"}]})
The JSON pretty printed:
{
"data": null,
"errors": [{
"path": null,
"locations": [{
"line": 3,
"column": 5,
"sourceName": null
}],
"message": "Validation error of type FieldUndefined: Field 'mee' in type 'Query' is undefined # 'mee'"
}]
}
The code which is mostly stolen from How to serialize a generic class with kontlinx.serialization? :
class ApiResponse<T>(
#SerialName("data")
val data: T? = null,
#SerialName("errors")
val errors: List<ErrorResponse>? = null
)
#Serializable
class ErrorResponse(
val path: String? = null,
val locations: List<Location>? = null,
val errorType: String? = null,
val message: String? = null
)
#Serializable
data class Location(
val line: Int? = 0,
val column: Int? = 0,
val sourceName: String? = null
)
#ExperimentalSerializationApi
class ApiResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<ApiResponse<T>> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ApiResponseDataSerializer") {
val dataDescriptor = dataSerializer.descriptor
element("data", dataDescriptor)
element("errors", ErrorResponse.serializer().descriptor)
}
override fun deserialize(decoder: Decoder): ApiResponse<T> =
decoder.decodeStructure(descriptor) {
var data: T? = null
var errors: List<ErrorResponse>? = null
val listSerializer = ListSerializer(ErrorResponse.serializer())
loop# while (true) {
when (val i = decodeElementIndex(descriptor)) {
0 -> data = decodeSerializableElement(descriptor, i, dataSerializer, null)
1 -> errors = decodeSerializableElement(descriptor, i, ListSerializer(ErrorResponse.serializer()), null)
CompositeDecoder.DECODE_DONE -> break
else -> throw SerializationException("Unknown index $i")
}
}
ApiResponse(data, errors)
}
override fun serialize(encoder: Encoder, value: ApiResponse<T>) {
encoder.encodeStructure(descriptor) {
val listSerializer = ListSerializer(ErrorResponse.serializer())
encodeNullableSerializableElement(descriptor, 0, dataSerializer, value.data)
value.errors?.let {
encodeNullableSerializableElement(descriptor, 1, listSerializer, it)
}
}
}
}
I tried using decodeNullableSerializableElement, but I got a compilation error. I couldn't find a way to fix that.
Type mismatch: inferred type is KSerializer<T> but DeserializationStrategy<TypeVariable(T)?> was expected
Any help would be appreciated, I am very new to Android and Kotlin.
Always pays to come back after a good nights sleep. Not sure why I had so much trouble with decodeNullableSerializableElement yesterday, but today I played around and got it working.
Made 3 changes
Made T optional in class parameter
Added .nullable (could not get this to work yesterday) to both serialisers
Changed decodeSerializableElement to decodeNullableSerializableElement
Relevant changes below:
#ExperimentalSerializationApi
class ApiResponseSerializer<T>(private val dataSerializer: KSerializer<T?>) : KSerializer<ApiResponse<T>> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ApiResponseDataSerializer") {
val dataDescriptor = dataSerializer.descriptor
element("data", dataDescriptor.nullable)
element("errors", ErrorResponse.serializer().descriptor.nullable)
}
override fun deserialize(decoder: Decoder): ApiResponse<T> =
decoder.decodeStructure(descriptor) {
var data: T? = null
var errors: List<ErrorResponse>? = null
val listSerializer = ListSerializer(ErrorResponse.serializer()).nullable
loop# while (true) {
when (val i = decodeElementIndex(descriptor)) {
0 -> data = decodeNullableSerializableElement(descriptor, i, dataSerializer, null)
1 -> errors = decodeNullableSerializableElement(descriptor, i, listSerializer, null)
CompositeDecoder.DECODE_DONE -> break
else -> throw SerializationException("Unknown index $i")
}
}
ApiResponse(data, errors)
}
override fun serialize(encoder: Encoder, value: ApiResponse<T>) {
encoder.encodeStructure(descriptor) {
val listSerializer = ListSerializer(ErrorResponse.serializer())
encodeNullableSerializableElement(descriptor, 0, dataSerializer, value.data)
value.errors?.let {
encodeNullableSerializableElement(descriptor, 1, listSerializer, it)
}
}
}
}

Create customise Data class model using Kotlin Koin

I'm new to Kotlin & understanding the concepts as I move. Stuck in creating one type of Data class model where the response json structure as shown below
data class SPLPlayer(
#field:Json(name ="id") val playerId: String?,
val type: String?,
#field:Json(name ="value") val currentValue: String?,
#field:Json(name ="Confirm_XI") val isIn_XI: Boolean = false,
#field:Json(name ="Matches") val totalMatchs: String?,
#field:Json(name ="Position") val position: String?,
#field:Json(name ="Skill") val skill: String?,
#field:Json(name ="skill_name") val skillName: String?,
val teamId: String?,
val name: String?, // other keys to refer Name_Full, short_name
#field:Json(name ="Bowling") val bowler: SPLBowler? = null,
#field:Json(name ="Batting") val batsmen: SPLBatsmen? = null
)
data class SPLTeamInfo (
**How to parse the Team object which is dictionary**
)
Thanks & appreciate to every reader. Looking forward for the solution.
You should be able to use your own deserializer by adding annotation to a setter #set:JsonDeserialize() and passing your own deserializer implementation.
along the lines of:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.JsonDeserializer
.. rest of imports
// for a given simplified json string
val json: String = """{"teams":{"1":{"name":"foo"},"2":{"name":"bar"}}}"""
class MyDeserializer : JsonDeserializer<List<Team>> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): List<Team>? {
// iterate over each json element and return list of parsed teams
}
}
data class JsonResp (
#set:JsonDeserialize(using = MyDeserializer::class)
var teams: List<Team>
)
data class Team (
var id: String, // this is going to be a team key
var name: String
)
Tried GitHub search with query #set:JsonDeserialize and it shows thousands of examples.