Cannot get data from JSON of api in Kotlin - android

I have to use Kotlin for a project. Lets say I have some object called Note. I made the file in the following way
#Parcelize
data class Notes (
val notes: List<Note>
): Parcelable
#Parcelize
data class Note (
val id: Int,
val text: String
): Parcelable
I noticed that if the JSON returned is structured as follows and I also add #SerializedName("all_notes")
{
"all_notes": [
{
"id": 1,
"text": "Some text"
},
it works, but if the JSON returned is structured like this and i delete the #SerializedName notation
[
{
"id": 1,
"text": "Some text"
},
i cant get the data.
I'm guessing I have to somehow use the serialized name but I'm not sure how to. I need to use the second type of JSON file. Can anyone help?
Edit: Someone gave the idea of posting the error I get so here it is
The error I get now is
Error: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was
BEGIN_ARRAY at line 1 column 2 path $
As I said before, it works when I use the other JSON file. It also works normally if I queue for just one object from the API and I say that Notes is just the variables from the Note. I get no error and I'd get that one Note printed out.

I don't see a problem with your data classes neither with your json. except that you don't need the first data class. You can map your json directly using your second class since your json will not respond with a list.
This is may be of a help to your. Use this website to construct a kotlin data class for your json responses.
You don't need to use SerializedName annotation unless your data class fields have a different name than your json attributes, because it's just a mapper hook used by Gson when mapping json into your class instance.
Please clarify your problem more if the above didn't help you, we need to know what's is exactly your problem or stacktrace if you have any.

Related

How does differentiate between Serialized data class and normal Serialized class?

While writing code for RecyclerView to get data I figured out there's a data class in Kotlin.
Following codes are taken from two different projects which are linked above.
#Serializable
data class MarsPhoto(
val id: String,
#SerialName(value = "img_src")
val imgSrc: String
)
class Contacts {
#SerializedName("country")
private val country:String? = null
fun getCountry():String?{
return country
}
}
I know that both classes are doing same job. So what does differentiate them? I also wonder in the MarsPhoto data class how they can get the id without declaring SerialName just the way they did for imgSrc. (I am just on the way to learning kotlin now, so I'm absolute beginner).
Basically for "data" class the compiler automatically derives the following members from all properties declared in the primary constructor:
equals()/hashCode() pair
toString() of the form "MarsPhoto(id=1, imgSrc=asdf)"
componentN() functions corresponding to the properties in their order of declaration.
copy()
You can read a lot more at enter link description here
On the SerializedName part of your question. if you are dealing with Gson lib by default it is using fields name as "SerializedName". And only if you want to use something different then field name, you can use SerializedName annotation and pass your custom value there. But usually, everybody just writes #SerializedName() with duplication of field names as value for every field.
It's a good idea if you are receiving and Serializing data from server from Json. Because Backend developers can use a bad keys in response, which you don't want to use in your code, so #SerializedName will be the only place where you will have to see this key, and you can name your fields however you like.
#Serializable used to mark class as serializable to disk or like into a file( alternative is Parcel able in android) special useful in case of process death or configuration changes and #SerializedName("country") used for json parsing when u receive the response from server
You get the id without #SerializedName because the JSON property field is the same as your variable name, but imgSrc and img_src is not. Still, even if they are the same, you should always use #SerializedName, because your variable names could be converted to random letters during code optimization, and obfuscation.

how to create data class with this dynamic json keys while using retrofit and andorid jetpack libraries

this is json output:
{"query":{"apikey":"...","base_currency":"USD","timestamp":1635972203},"data":{"JPY":113.99127,"CNY":6.39464,"CHF":0.9114,"CAD":1.23881,"MXN":20.54423,"INR":74.44808,"BRL":5.57063,"RUB":71.80098,"KRW":1175.11443,"IDR":14295.1734,"TRY":9.63691,"SAR":3.75119,"SEK":8.52554,"NGN":410.22181,"PLN":3.94541,"ARS":99.81213,"NOK":8.49529,"TWD":27.78459,"IRR":42000.64577,"AED":3.67284,"COP":3827.77643,"THB":33.32047,"ZAR":15.23269,"DKK":6.40357,"MYR":4.15212,"SGD":1.34783,"ILS":3.11624,"HKD":7.78416,"EGP":15.7003,"PHP":50.65881,"CLP":811.73282,"PKR":169.4547,"IQD":1458.01958,"DZD":136.722,"KZT":428.93534,"QAR":3.6499,"CZK":21.94293,"PEN":4.0008,"RON":4.25921,"VND":22747.41599,"BDT":85.57148,"HUF":308.78687,"UAH":26.25062,"AOA":598.0065,"MAD":9.06226,"OMR":0.38491,"CUC":24.00026,"BYR":2.00003,"AZN":1.69502,"LKR":200.00259,"SDG":438.90856,"SYP":2511.07513,"MMK":1746.02836,"DOP":56.29093,"UZS":10690.31508,"KES":111.25137,"GTQ":7.73108,"URY":44.18107,"HRV":6.47553,"MOP":8.01811,"ETB":47.31305,"CRC":635.74442,"TZS":2298.03956,"TMT":3.49009,"TND":2.80635,"PAB":1.00002,"LBP":1505.5263,"RSD":101.16202,"LYD":4.54568,"GHS":6.00013,"YER":249.956,"BOB":6.82018,"BHD":0.377,"CDF":1999.22628,"PYG":6875.19435,"UGX":3550.05822,"SVC":8.7497,"TTD":6.74137,"AFN":90.84208,"NPR":119.13277,"HNL":24.06657,"BIH":1.68483,"BND":1.34753,"ISK":129.16264,"KHR":4060.117,"GEL":3.14003,"MZN":63.22108,"BWP":11.45513,"PGK":3.5113,"JMD":153.22216,"XAF":564.86281,"NAD":15.2189,"ALL":105.53113,"SSP":391.0052,"MUR":42.90097,"MNT":2830.04693,"NIO":35.21094,"LAK":10330.27262,"MKD":53.08156,"AMD":474.80501,"MGA":3928.06091,"XPF":102.48118,"TJS":11.26034,"HTG":98.0013,"BSD":1.00003,"MDL":17.41883,"RWF":1018.02194,"KGS":84.77099,"GNF":9510.20822,"SRD":21.40242,"SLL":10779.18736,"XOF":568.81159,"MWK":807.36713,"FJD":2.06806,"ERN":15.05028,"SZL":15.21372,"GYD":207.78611,"BIF":1980.25293,"KYD":0.82002,"MVR":15.42042,"LSL":15.23032,"LRD":146.80405,"CVE":94.95278,"DJF":177.50237,"SCR":14.42749,"SOS":575.00647,"GMD":52.15123,"KMF":424.6543,"STD":21.11031,"XRP":0.83002,"AUD":1.34372,"BGN":1.68394,"BTC":0.0159,"JOD":0.70801,"GBP":0.73402,"ETH":0.00022,"EUR":0.86112,"LTC":0,"NZD":1.40184}}
The data section contains many key value pairs, but their number and names vary according to base_currency. (For example if i send request with USD there is no USD key or if i send request with CNY there is no CNY key in data section)
So what kind of data class should I create so that I can use it with the retrofit and gsoncreator libraries. (I am also trying to use and learn jetpack android libraries if this is important)
i use that data classes: (I am not using gson annotations because i believe my variables names are correct and i try that it doesn't help)
data class ResponseFromApi(val data: Data,val query: Query)
data class Query(val apikey: String, val base_currency: String, val timestamp: Int)
data class Data(val hashmapForData: HashMap<String, Double>) (i suspect some values are integer but i am not sure)
it doesn't work. Maybe that's not the problem. I don't know but least i need to know, Are these classes correct? What is the proper way to do this.
And i don't know how to get error message from retrofit object so i can identify the problem. But this is another question.
Arpit Shukla's answer is correct.
ResponseFromApi(val query: Query, val data: Map<String,Double>)
It can deserialize key-value map to Map struct.

Android. How to parse JsonArrays inside JsonArray

I use retrofit and json. I need to parse a json like that:
{
"outer_array": [
"one" : [{}, {}],
"two" : [{}],
"three" : [{}],
....
]
}
Here is part of my code:
#SerializedName("outer_array") var data: List<List<Data>>
But how can I parse each element of the "outer_array" array by key. Arrays inside "outer_array" can be from 0 to n. Maybe I should use a TypeAdapter. But I don't know how to apply it for this case, please help me
The JSON you've provided is not valid. You can check it on JSONLint. I guess it should be either an array (without "one" :) or an object ([ in front of outer_array should be {) in the first case your code is fine and in second case you can use a Map (like HashMap) and if you need to convert that map to array you can use something like this
data class MyClass(
#SerializedName("outer_array") var data: HashMap<String, List<Data>>
){
val dataAsArray get() = data.map{ it.value }
}
But if you have to parse this invalid data (e.g you cannot ask the provider/backend to fix this) even a TypeAdapter cannot help, because the JSON is invalid and you'll face an exception, you can use a regex to replace invalid [ ] with { } (but this is so dumb, try to convince the backend dev to fix the json :D)
I suggest you to use Gson, a Google service to parse JSON strings into Objects and viceversa. Simple add "implementation 'com.google.code.gson:gson:2.8.6'" into dependencies in the app's Gradle file in your Android project.
You could use two methods: toJson(Object obj) and fromJson(string json, Class<>)
if you have a class called 'MyObject', you have to put as second parameter of 'fromJson': MyObject.class, or MyObject[].class if an array.
GitHub: https://github.com/google/gson

How to skip JSON properties in Moshi?

I'm trying to implement JSON parsing in my Android application written in Kotlin using com.squareup.moshi (v1.10.0).
Within the JSON file there are some properties that are not interesting in my case. Let's say, I only need the position to be able to mark the place on a map and the JSON looks like this:
"location":{
"address":{
"country":"..."
},
"position":{
"lat":47.469866,
"lon":19.062435
}
}
If I'm right, the data class in Kotlin should look like this if I'd like to parse that JSON:
#Parcelize
data class Location(
val address: Address,
val position: Position
): Parcelable
#Parcelize
data class Address(
val country: String
): Parcelable
#Parcelize
data class Position(
val lat: Double,
val lon: Double
): Parcelable
In Moshi's documentation I could find the transient keyword to skip values which in Kotlin works as an annotation (#Transient). As the documentation says:
Transient fields are omitted when writing JSON. When reading JSON, the field is skipped even if the JSON contains a value for the field. Instead it will get a default value.
Does it mean that if I don't want to have the address object, I should use the following code?
#Parcelize
data class Location(
#Transient val address: Address? = null,
val position: Position
): Parcelable
Also, what about in general terms? What if I have huge list of properties within a JSON object but I know I only need the 'position' object? Do I still have to create null values to parse the JSON file field-by-field?
I think you are looking for something similar to GSON's #Expose annotations, wherein all model fields are excluded from parsing except those annotated.
This functionality is currently not available in Moshi, so your current implementation using the #Transient annotation seems to be the most optimal solution. (See Moshi issues conversation here.)
Extra food for thought:
You may also wish to use #IgnoredOnParcel on your transient fields since you are implementing the parcelable interface. (Have a look here for some implementation pointers.)
Alternatively you could separate your data model into 2 models - one for use in your app and one which reflects the server (JSON) schema (just as you have done above). The main data model for your app (which could implement parcelable) would contain only the fields you use (for example, the position field). When you parse your data, you then convert that data to your primary data model using some simple adapter. (This is often good practice anyhow, since server-side schemas are inherent to change. This way, any changes in the JSON schema wouldn't end having any ripple effect throughout your code.)
https://github.com/square/moshi#omit-fields-with-transient
Omit fields with transient
Some models declare fields that shouldn’t be included in JSON. For example, suppose our blackjack hand has a total field with the sum of the cards:
public final class BlackjackHand {
private int total;
...
}
By default, all fields are emitted when encoding JSON, and all fields are accepted when decoding JSON. Prevent a field from being included by adding Java’s transient keyword:
public final class BlackjackHand {
private transient int total;
...
}
Transient fields are omitted when writing JSON. When reading JSON, the field is skipped even if the JSON contains a value for the field. Instead it will get a default value.

Swift to Kotlin: Struct to Data Class Example

I have converted the following Swift code:
struct FooModel: Decodable {
public let id: String
public let bars: [[BarModel]]
}
to this Kotlin code:
data class FooModel (val id: String, val bars: List<List<BarModel>>)
The issue I am encountering, is my id is coming in null for the Kotlin code (via gson). Everything else in the Kotlin conversion is working fine and the entire JSON is populating all data classes, except for this tiny piece (the id variable).
I suspect my conversion here is the cause, any ideas?
If the id should be nullable do it like this:
data class FooModel (
val id: String?,
val bars: List<List<BarModel>>
)
The question mark makes this property nullable.
If the JSON you are getting is correct (the id value is there and coming to you as a string), your code should work. It's unclear what could be going wrong here if that's the case.
However, it is worth knowing that there is a big potential "gotcha" with Gson that you should be aware of: it's possible to declare a variable of a data class as non-nullable but still get a null after conversion. This can happen when an expected value is missing from the JSON response. In these cases Gson does not throw an error and I only found out later when I got a crash trying to access the non-nullable variable that should never have made it to me as null. I discovered this is a consequence of Gson using something like Class.newInstance() instead of a regular constructor when it creates these data classes, and then uses reflection to populate the data. More is written about this in another answer here: Why Kotlin data classes can have nulls in non-nullable fields with Gson?
Depending on your use case you might consider this to be a design flaw and a reason to avoid Gson in favor of other JSON serialization libraries. My personal favorite at the moment is Square's Moshi.
You can check if the value type you are getting from server matches with your variable id i.e. String on both the sides. Secondly you can try using SerializedName("id") included in library:
implementation 'com.google.code.gson:gson:2.9.0'

Categories

Resources