GSON throwing Expected BEGIN_OBJECT but was BEGIN_ARRAY - kotlin

Facing this problem past few days.Does any guys check this issue? Any help would be greatly appreciated.How can I solved this problem?
GSON throwing Expected BEGIN_OBJECT but was BEGIN_ARRAY
Problem coming from
override fun onSuccess(str_SUCCESS: String)
{
System.out.println("JSON_IS"+str_SUCCESS)
val paymentScheduleModel = Gson().fromJson<PaymentScheduleModel>(str_SUCCESS, PaymentScheduleModel::class.java) // Problem here
}
Json Response is
{
"status": {
"statusCode": 10016,
"isSuccess": true,
"message": "Success"
},
"data": {
"payback_schedule": [
{
"id": 2,
"paid_amount": "INR NaN",
"paidStatus": "Upcoming Payback",
"paid_status": "P",
"s_date": "05/01/2018 12:31:10",
"e_date": "11/01/2018 12:31:10",
"current_balance": "INR 399",
"payanytime_button_status": "active",
"btnColor": "red",
"btnHexColor": "#D2322D"
},
{
"id": 3,
"paid_amount": "INR NaN",
"paidStatus": "Upcoming Payback",
"paid_status": "P",
"s_date": "12/01/2018 12:31:10",
"e_date": "18/01/2018 12:31:10",
"current_balance": "INR 399",
"payanytime_button_status": "active",
"btnColor": "red",
"btnHexColor": "#D2322D"
}
]
}
}
PaymentScheduleModel
data class PaymentScheduleModel(#SerializedName("payback_schedule") val payback_schedule: PaymentSchedule)
data class PaymentSchedule
(#SerializedName("id") val id: Int,
#SerializedName("paid_amount") val paid_amount:String,
#SerializedName("paidStatus") val paidStatus:String,
#SerializedName("paid_status") val paid_status:String,
#SerializedName("s_date") val s_date:String,
#SerializedName("e_date") val e_date:String,
#SerializedName("current_balance") val current_balance:String,
#SerializedName("payanytime_button_status") val payanytime_button_status:String,
#SerializedName("btnColor") val btnColor:String,
#SerializedName("btnHexColor") val btnHexColor:String)

Your model object does not match your Json.
You are trying to parse a JsonObject PaymentScheduleModel which has sub object "payback_schedule" of type PaymentSchedule but you have a JsonObject which has a sub object "data" which is what has the sub object "payback_schedule". So really, you want to parse the "data" block.
You have two options:
1: Create another model that wraps the data block and parse that:
data class PaymentScheduleData(#SerializedName("data") val payback_schedule_model: PaymentScheduleModel)
override fun onSuccess(str_SUCCESS: String) {
val paymentScheduleData = Gson().fromJson<PaymentScheduleData>(str_SUCCESS, PaymentScheduleData::class.java)
// Now use paymentScheduleData.payback_schedule_model
}
2: Pull out the data portion first, then parse:
override fun onSuccess(str_SUCCESS: String) {
// Get the root JsonObject
val jsonObject = Gson().fromJson<JsonObject>(str_SUCCESS, JsonObject::class.java)
// Get the "data" block that matches the model and parse that
val paymentScheduleModel = Gson().fromJson<PaymentScheduleModel>(jsonObject.getAsJsonObject("data"), PaymentScheduleModel::class.java)
}
Hope that helps!

The error is telling you that payback_schedule is holding an array instead of object. So, payback_schedule should be Array<PaymentSchedule> instead of PaymentSchedule.
data class PaymentScheduleModel(#SerializedName("payback_schedule") val payback_schedule: Array<PaymentSchedule>)
PS. You are suggested to implement your own equals() and hashCode() function if your data class contains Array because the default implementation of Array's equals() function compares the referential equality. Suggested reading: Equals method for data class in kotlin

Related

Deserialize JSON from Riot API with kotlinx.serialization

I have some difficulties to deserialise this JSON object from RIOT API:
{
"type":"champion",
"version":"6.1.1",
"data":{
"Thresh":{
"id":412,
"key":"Thresh",
"name":"Thresh",
"title":"the Chain Warden"
},
"Aatrox":{
"id":266,
"key":"Aatrox",
"name":"Aatrox",
"title":"the Darkin Blade"
},...
}
}
Inside the data object we have an other object with fields of all champions.
To not create all champions objects, I want de deserialise this to an list of Champion object, I expect something like that:
{
"type":"champion",
"version":"6.1.1",
"data":[
{
"id":412,
"key":"Thresh",
"name":"Thresh",
"title":"the Chain Warden"
},
{
"id":266,
"key":"Aatrox",
"name":"Aatrox",
"title":"the Darkin Blade"
},...
]
}
I think I have to create a custom Serializer that extends KSerialize but I didn't really know how to do it, can someone help me ?
On C# stackoverflow response is : Deserialize JSON from Riot API C#
There is my solution:
(If someone know witch descriptor to put there I'm interested)
object ChampionsSerializer : KSerializer<List<NetworkChampion>> {
// TODO : Not the good descriptor, fix me
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("data", kind = PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): List<NetworkChampion> {
val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON")
val fieldsAsJson = jsonInput.decodeJsonElement().jsonObject
val jsonParser = jsonInput.json
return fieldsAsJson.map {
jsonParser.decodeFromJsonElement(it.value)
}
}
override fun serialize(encoder: Encoder, value: List<NetworkChampion>) {
}
}
#Serializable
data class NetworkChampionsResponse(
val type: String,
val format: String,
val version: String,
#Serializable(ChampionsSerializer::class)
val data: List<NetworkChampion>
)
Json link:
https://ddragon.leagueoflegends.com/cdn/13.1.1/data/fr_FR/champion.json

Deserialize empty json value to null with kotlinx.serialization

I have the following response from a backend:
{
"title": "House",
"translations": {
"es": "Casa",
"fr": "Maison",
"de": "Haus"
}
}
To process it I am using the kotlinx serializer and this is my data class.
#Serializable
data class MyRespons(
val title: String,
val translations: Map<String,String>? = null,
)
The property translations is optional, so in some cases I can just get the title (which is fine). What the problem is, is that there also cases where the backend returns this json:
{
"title": "House",
"translations": ""
}
This throws an error because Kotlin is not converting the empty string to a null map but tries to get the properties from it. Is there a way to make Kotlin treat an empty string as if the property was not set at all? (I am trying to not make a custom serializer for this, especially because the map serializer has lots of code...)
Sadly I can't change this backend behavior and have to live with it.
you can wrap Map Serializer with:
object MapSerializer: KSerializer<Map<String,String>> {
override val descriptor: SerialDescriptor
get() = TODO("Not yet implemented")
override fun deserialize(decoder: Decoder): Map<String, String> {
if (decoder.decodeString().isEmpty())
return mapOf()
else
return MapSerializer(String.serializer(),String.serializer()).deserialize(decoder)
}
}

Convert JSON string to Map with any primitive type or JSONObject

I am trying to convert provided string (JSON) to Map<String,Any> so for example JSON can be like that (value can be any primitive type or a collection such as map set or array :
{
"key": "thisIsMyKey",
"value": false
}
So i did convert it using GSON with this little snippet:
return jsonMap?.let { Gson().fromJson(jsonMap, object : TypeToken<HashMap<String, Any>>() {}.type) }
The issue i am having is now for following example if i want to pass value with JSONObject
{
"key": "thisIsMyKey",
"value": {
"title": "this is title"
}
}
It gets converted to one key and multiple "Any" values with string, but in this case i expect that value is than just one entry of <String, JSONObject), any tips how to achieve this one level too deep converting?
You may do this by specifying expected type as Map<String, JsonElement>. Downside of this approach is that values of primitive types (Number, String, Boolean) would be wrapped into JsonPrimitive. If it's unacceptable, you may manually unwrap them:
val result = jsonMap?.let {
Gson().fromJson<Map<String, JsonElement>>(jsonMap).mapValues { (_, v) -> deprimitivize(v) }
}
fun deprimitivize(e: JsonElement) = if (e is JsonPrimitive) e.getValue() else e
fun JsonPrimitive.getValue(): Any = when {
isBoolean -> asBoolean
isNumber -> asNumber
else -> asString
}
inline fun <reified T : Any> Gson.fromJson(json: String): T = fromJson(json, object : TypeToken<T>() {}.type)

Jackson cannot deserialize some non-empty fields

I have a problem with the object deserialization.
My DTO contains a list of Pairs (the previous version was Map).
data class MyDto(
#JsonIgnoreProperties(ignoreUnknown = true)
val myField: List<Pair<String, Boolean>>?
)
And I constantly receive a MissingKotlinParameterException
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class kotlin.Pair<java.lang.String,java.lang.Boolean>] value failed for JSON property first due to missing (therefore NULL) value for creator parameter first which is a non-nullable type\n at [Source: (io.netty.buffer.ByteBufInputStream); line: 9, column: 33] (through reference chain: my.path.MyDto[\"field\"]->java.util.ArrayList[0]->kotlin.Pair[\"first\"])\n\tat org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:162)
my json looks like:
{
"myField" : [
"A": true,
"B": false
]
}
As you can see I have already made the list nullable and put an annotation#JsonIgnoreProperties. But still I get the error.
My configuration for the objectMapper
#Bean
#Primary
fun objectMapper(): ObjectMapper = jacksonObjectMapper().apply {
findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES)
.disable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)
.disable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.enable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
}
What should I also enable/disable to make it work?
Try replacing
List<Pair<String, Boolean>>
with
Map<String, Boolean>
And change the JSON to:
{
"myField" : {
"A": true,
"B": false
}
}
Should work as intended now.

Register deserializer for a wrapped List of type

I have JSON that is like this:
{
"apps": [
{
"id": "1",
...
},
{
"id": "2",
...
}
]
}
And for example say the Application class looks like this
data class Application(
val id: String
)
I want to deserialize the JSON into a List<Application>, where each {...} is an Application. I was hoping to do this without having to create a wrapper class like Applications, annotating it with #JsonRootName, and then enabling DeserializationFeature.UNWRAP_ROOT_VALUE. The end goal is to have a Retrofit interface that has something like:
#GET("api/apps")
fun listApplications(): Call<List<Application>>
I tried to implement a simple JsonDeserializer (could probably be optimized):
class ApplicationListDeserializer
: JsonDeserializer<List<Application>>() {
companion object {
private val COLLECTION_TYPE: CollectionType = TypeFactory.defaultInstance()
.constructCollectionType(List::class.java, Application::class.java)
}
override fun deserialize(parser: JsonParser, context: DeserializationContext): List<Application> {
val mapper = ObjectMapper()
val node: JsonNode = parser.codec.readTree(parser)
val collectionReader = mapper.readerFor(COLLECTION_TYPE)
return collectionReader.readValue(node.get("apps"))
}
}
I don't see any way to register this deserializer for this specific type. I tried this:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
val objectMapper = ObjectMapper().registerModule(KotlinModule())
val module = SimpleModule()
module.addDeserializer(List::class.java, ApplicationListDeserializer())
objectMapper.registerModule(module)
val applications: List<Application> = objectMapper.readValue("""{
"apps": [
{
"id": "/api-catalog-backend"
}
]
}""")
But that fails with:
Can not construct instance of Application: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: N/A; line: -1, column: -1] (through reference chain: java.util.ArrayList[0])" type="com.fasterxml.jackson.databind.JsonMappingException"><![CDATA[com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of Application: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: N/A; line: -1, column: -1] (through reference chain: java.util.ArrayList[0])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012)
I am using the following dependency versions:
compile("com.fasterxml.jackson.core", "jackson-core", "2.8.6")
compile("com.fasterxml.jackson.module", "jackson-module-kotlin", "2.8.4")
compile(kotlinModule("stdlib", "1.1-M03"))
compile(kotlinModule("reflect", "1.1-M03"))
How do I configure the deserialization to work correctly with a List<Application>?