mapped data from json in kotlin shows wrong value - android

I need to show this data in my sample kotlin app.
I have created all appropriate models using moshi. Which can be found here.
But the values are showing wrong. Even non null values are showing as null.
Example json, value in 0 index:
"main": {
"temp": 25.78,
"feels_like": 25.95,
"temp_min": 23.35,
"temp_max": 25.78,
"pressure": 1014,
"sea_level": 1014,
"grnd_level": 1012,
"humidity": 59,
"temp_kf": 2.43
},
The model responsible:
#Parcelize
#JsonClass(generateAdapter = true)
data class Main(
#Json(name = "temp")
val temp: Double?,
#Json(name = "feels_like")
val feelsLike: Double?,
#Json(name = "temp_min")
val tempMin: Double?,
#Json(name = "temp_max")
val tempMax: Double?,
#Json(name = "pressure")
val pressure: Double?,
#Json(name = "sea_level")
val seaLevel: Double?,
#Json(name = "grnd_level")
val grndLevel: Double?,
#Json(name = "humidity")
val humidity: Double?,
#Json(name = "temp_kf")
val tempKf: Double?,
) : Parcelable {
fun getTempString(): String {
return temp.toString().substringBefore(".") + "°"
}
fun getHumidityString(): String {
return humidity.toString() + "°"
}
fun getTempMinString(): String {
return tempMin.toString().substringBefore(".") + "°"
}
fun getTempMaxString(): String {
return tempMax.toString().substringBefore(".") + "°"
}
}
But when I print the main in 0 index shows:
Main(temp=298.26, feelsLike=null, tempMin=null, tempMax=null, pressure=1014.0, seaLevel=null, grndLevel=null, humidity=62.0, tempKf=null)
As a result the views are not working either.
The entire source code can be found here.

See discussion here, it might be that the following anotation #field:Json(name = "temp_min") instead of #Json(name = "temp_min"), and the same for the other members, could fix it. Alternatively, you can try to use a different version of Moshi. Also, see related discussion Moshi #Json annotation not working for com.github.kittinunf.fuel.moshi.moshiDeserializerOf?

Related

How can I use Retrofit to parse a JSON response from an API, that has an embedded JSON object?

I'm trying to learn Android Development by making a bus arrival timing application that makes API calls to a local API that has the arrival timings for the next 3 buses. I am using Kotlins and Jetpack Compose to help. This is a sample of the JSON response I get:
{
"odata.metadata": "http://datamall2.mytransport.sg/ltaodataservice/$metadata#BusArrivalv2/#Element",
"BusStopCode": "65199",
"Services": [
{
"ServiceNo": "89",
"Operator": "SBST",
"NextBus": {
"OriginCode": "64009",
"DestinationCode": "64009",
"EstimatedArrival": "2022-12-22T22:15:06+08:00",
"Latitude": "1.3947326666666666",
"Longitude": "103.89898083333334",
"VisitNumber": "1",
"Load": "SEA",
"Feature": "WAB",
"Type": "DD"
},
"NextBus2": {
"OriginCode": "64009",
"DestinationCode": "64009",
"EstimatedArrival": "2022-12-22T22:31:36+08:00",
"Latitude": "0",
"Longitude": "0",
"VisitNumber": "1",
"Load": "SEA",
"Feature": "WAB",
"Type": "DD"
},
"NextBus3": {
"OriginCode": "64009",
"DestinationCode": "64009",
"EstimatedArrival": "2022-12-22T22:47:51+08:00",
"Latitude": "0",
"Longitude": "0",
"VisitNumber": "1",
"Load": "SEA",
"Feature": "WAB",
"Type": "DD"
}
}
]
}
I have been following the code labs on Android Documentation and tried to store the results into a Data Class in Android as shown below. I don't think I am doing it right for the JSON objects NextBus, NextBus2 and NextBus3. Thank you
SingaporeBus.kt
package com.example.busexpress.network
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
#Serializable
data class SingaporeBus(
#SerialName(value = "ServiceNo")
val busServiceNumber: String,
// Bus Arrival Timings
#SerialName(value = "NextBus")
val nextBus1: Json,
#SerialName(value = "NextBus2")
val nextBus2: Json,
#SerialName(value = "NextBus3")
val nextBus3: Json
)
#Serializable
data class NextBusTiming(
// Date-Time expressed in the UTC standard, GMT+8 for Singapore Standard Time (SST)
#SerialName(value = "EstimatedArrival")
val estimatedArrival: String,
#SerialName(value = "OriginCode")
val startingBusStop: String,
#SerialName(value = "DestinationCode")
val endingBusStop: String,
// Current Bus Occupancy Levels
#SerialName(value = "Load")
val busOccupancyLevels: String,
// Wheelchair Support
#SerialName(value = "Feature")
val wheelchairAccessible: String,
// Bus Type
#SerialName(value = "Type")
val vehicleType: String,
// Bus Approximate Location
#SerialName(value = "Latitude")
val latitude: String,
#SerialName(value = "Longitude")
val longitude: String
)
Each object stored in Json should have its own representation in data class.
kotlinx.serialization automatically handles nested objects.
So your SingaporeBus model should look like this:
#Serializable
data class SingaporeBus(
#SerialName(value = "ServiceNo")
val busServiceNumber: String,
// Bus Arrival Timings
#SerialName(value = "NextBus")
val nextBus1: NextBusTiming,
#SerialName(value = "NextBus2")
val nextBus2: NextBusTiming,
#SerialName(value = "NextBus3")
val nextBus3: NextBusTiming
)
I hope this answer helps you
The code you've written should be like this
#Serializable
data class SingaporeBus(
#SerialName(value = "ServiceNo")
val busServiceNumber: String,
// Bus Arrival Timings
#SerialName(value = "NextBus")
val nextBus1: NextBusTiming,
#SerialName(value = "NextBus2")
val nextBus2: NextBusTiming,
#SerialName(value = "NextBus3")
val nextBus3: NextBusTiming
)
#Serializable
data class NextBusTiming(
// Date-Time expressed in the UTC standard, GMT+8 for Singapore Standard Time (SST)
#SerialName(value = "EstimatedArrival")
val estimatedArrival: String,
#SerialName(value = "OriginCode")
val startingBusStop: String,
#SerialName(value = "DestinationCode")
val endingBusStop: String,
// Current Bus Occupancy Levels
#SerialName(value = "Load")
val busOccupancyLevels: String,
// Wheelchair Support
#SerialName(value = "Feature")
val wheelchairAccessible: String,
// Bus Type
#SerialName(value = "Type")
val vehicleType: String,
// Bus Approximate Location
#SerialName(value = "Latitude")
val latitude: String,
#SerialName(value = "Longitude")
val longitude: String
)
Although The code can hove some improvements :
1.If you used Retrofit you would be able to use #SerializedName instead of #SerialName and in that situation your class should use
#Parcelize and the data class implemented Parcelable interface
this is retrofit sample for your api call :
#Parcelize
data class SingaporeBus(
#SerializedName(value = "ServiceNo")
val busServiceNumber: String,
// Bus Arrival Timings
#SerializedName(value = "NextBus")
val nextBus1: NextBusTiming,
#SerializedName(value = "NextBus2")
val nextBus2: NextBusTiming,
#SerializedName(value = "NextBus3")
val nextBus3: NextBusTiming
) : Parcelable
#Parcelize
data class NextBusTiming(
#SerializedName(value = "EstimatedArrival")
val estimatedArrival: String,
#SerializedName(value = "OriginCode")
val startingBusStop: String,
#SerializedName(value = "DestinationCode")
val endingBusStop: String,
#SerializedName(value = "Load")
val busOccupancyLevels: String,
#SerializedName(value = "Feature")
val wheelchairAccessible: String,
#SerializedName(value = "Type")
val vehicleType: String,
#SerializedName(value = "Latitude")
val latitude: String,
#SerializedName(value = "Longitude")
val longitude: String
) : Parcelable
2.It's better to not use value declaration in for example in
#SerialName(value = "ServiceNo")
3.If you access the server json fields shouldn't start by Caps for example "ServicesNo" and they could be camelCase
4.According to Uncle Bob naming of fields should be clear and doesn't have comments like this :
#Serializable
data class NextBusTiming(
#SerialName(value = "EstimatedArrival")
val estimatedArrival: String,
#SerialName(value = "OriginCode")
val startingBusStop: String,
#SerialName(value = "DestinationCode")
val endingBusStop: String,
#SerialName(value = "Load")
val busOccupancyLevels: String,
#SerialName(value = "Feature")
val wheelchairAccessible: String,
#SerialName(value = "Type")
val vehicleType: String,
#SerialName(value = "Latitude")
val latitude: String,
#SerialName(value = "Longitude")
val longitude: String
)
5.Finally you can have different classes for use them easier at other Classes for example NextBusTiming may have other usage in future and in this you can find it better than being in same class or Kotlin files actually.

Creating Domain Model mapper with complex DTO

I am using JSON to Kotlin plugin for generating DTO classes with Moshi to save a lot of time dealing with complex JSON response from APIs.
Just to give a glimpse how huge the response can be
Sample DTO using Moshi
#JsonClass(generateAdapter = true)
data class AssetDto(
#Json(name = "data")
val `data`: List<AssetData> = listOf(),
#Json(name = "status")
val status: Status = Status()
)
#Parcelize
#JsonClass(generateAdapter = true)
data class Status(
#Json(name = "elapsed")
val elapsed: Int = 0,
#Json(name = "timestamp")
val timestamp: String = ""
) : Parcelable
#Parcelize
#JsonClass(generateAdapter = true)
data class AssetData(
#Json(name = "id")
val id: String = "",
#Json(name = "metrics")
val metrics: Metrics = Metrics(),
#Json(name = "name")
val name: String = "",
#Json(name = "profile")
val profile: Profile = Profile(),
#Json(name = "symbol")
val symbol: String? = ""
) : Parcelable
Problems
I want to know what is the best way to create Domain Model out of a complex DTO class without manually encoding it.
Should I create Domain Model for AssetDto or AssetData? As you can see I have tons of value-object and I do not know if I should create a Domain Model on each of those value-object or it is okay to reuse the data class from DTO.
For now I generate another pile of plain data class using JSON to Kotlin which means I have dozens of identical data class and it looks like I am still oblige to manually set each values, this became a total blocker now. I am not sure if I should continue implementing mapper.
data class AssetDomain(
var status: Status? = Status(),
var `data`: List<Data>? = listOf()
)
data class Status(
var elapsed: Int? = 0,
var timestamp: String? = ""
)
data class Data(
var id: String? = "",
var metrics: Metrics? = Metrics(),
var name: String? = "",
var profile: Profile? = Profile(),
var symbol: String? = ""
)
You should create domain models based on your business logic not based on the response itself.

Compare two array lists of obejcts with different ids

What is the most optimized way to compare two ArrayLists of different object type? Let's say I have an Array Lists of Remedies and Adherences and them being:
ObjectA
data class ObjectA(val data: List<Objecta> = emptyList()) {
data class Objecta(
val _id: String,
val remedy_id: String,
val patient_id: String,
val date_created: Long,
val is_ongoing: Boolean?,
val start_date: Long,
val medicine_id: String,
val instruction: String,
val medicine_name: String,
val medicine_type: String,
val end_date: Long,
val repeat_cycle: Int,
val allow_edit: Boolean,
//val schedule: List<Schedule>?,
val is_current: Boolean,
//val medicine: Medicine?,
val price: Float
)
}
ObjectB
data class ObjectB(val data: List<Objectb> = emptyList()) {
data class Objectb(
val _id: String,
val adherence_id: String,
val patient_id: String,
val remedy_id: String,
val alarm_time: String,
val action: String,
val action_time: String,
val dose_discrete: String,
val dose_quantity: Int,
val note: String
)
}
How can I compare both those two array lists "remedyList" and "adherencesList" where remedy_id is the same and remove items in adherencesList when remedy_id is not present in remedyList.
This should work:
Prepare the input
data class Rdata (val r_id:String)
data class Adata (val a_id:String,val r_id:String)
val rList = (1..10).map { Rdata("$it")}
val aList = (5..20).map { Adata("foo", "$it")}
Do the filtering:
val rIds = rList.map { it.r_id }.toSet()
val resultList = aList.filter { it.r_id in rIds}
After this, the resultList contains objects with r_id 5-10. (that is, 11-20 have been removed)
Try this code:
val allRemedies = remedies.flatMap { it.data }.associateBy { it.remedy_id }
val allAdherences = adherences.flatMap { it.data }.associateBy { it.remedy_id }
val allPairs = allAdherences.mapValues { allRemedies[it.key] to it.value }
Playground example
remedies is the List<Remedies> and adherences is the List<Adherences>
First we flatten the List<Remedies> to List<Remedy> and associate each Remedy to its remedy_id. allRemedies is Map<String, Remedy>.
Similarly for the adherences, we prepare a Map<String, Adherence>
Then we map each value of allAdherences map to a pair of Remedy? and Adherence. If there is no Remedy for that remedy_id it will be null.
If all you care about is filtering Adherences that don't have a matching Remedy, you can create a Set of remedy_ids from the Remedy List, and use that to efficiently check if they exist and filter on that.
val validRemedyIds: Set<String> = remedies.data.mapTo(mutableSetOf(), Remedy::remedy_id)
val filteredAdherences = Adherences(adherences.data.filter { it.remedy_id in validRemedyIds })
If you need to match them up later, you should use a map instead of set:
val remediesById = remedies.data.associateBy(Remedy::remedy_id)
And then there are several ways you can combine to compare. One example:
val adherencesToRemedies: List<Pair<Adherence, Remedy>> =
adherences.data.mapNotNull { remediesById[it.remedy_id]?.run { it to this } }

How to parse newline delimited JSON with retrofit and moshi?

I'm trying to parse newline delimited json using retrofit and moshi. This is my GET function:
suspend fun getDeviceValuesNew(#Path("application-id") applicationId: String, #Path("device-id") deviceId: String)
: Response<List<ValueApiResponse>>
When I try to run it, I get this error:
com.squareup.moshi.JsonDataException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at path $
The HTTP call returns json like this:
{
"result": {
"end_device_ids": {
"device_id": "esp32",
"application_ids": {}
},
"received_at": "2021-03-31T11:33:42.757281753Z",
"uplink_message": {
"decoded_payload": {
"brightness": 0
},
"settings": {
"data_rate": {}
},
"received_at": "2021-03-31T11:33:42.547285090Z"
}
}
}
{
"result": {
"end_device_ids": {
"device_id": "esp32",
"application_ids": {}
},
"received_at": "2021-03-31T11:18:17.745921472Z",
"uplink_message": {
"decoded_payload": {
"brightness": 0
},
"settings": {
"data_rate": {}
},
"received_at": "2021-03-31T11:18:17.538276218Z"
}
}
}
EDIT #1:
As you can see in my answer below, I managed to get a valid JSON response from the API, but still I'm struggling to parse these JSON objects to a list of Kotlin objects. How do I get Moshi to handle these newline delimited JSON objects as a list? I think the problem is that Moshi requires the objects to be wrapped inside an array to be recognised as a list. How do I do that?
This is my data class used for parsing:
#JsonClass(generateAdapter = true)
data class ValueDto(
#Json(name = "result")
val result: Result
) {
#JsonClass(generateAdapter = true)
data class Result(
#Json(name = "end_device_ids")
val endDeviceIds: EndDeviceIds,
#Json(name = "received_at")
val receivedAt: String,
#Json(name = "uplink_message")
val uplinkMessage: UplinkMessage
) {
#JsonClass(generateAdapter = true)
data class EndDeviceIds(
#Json(name = "application_ids")
val applicationIds: ApplicationIds,
#Json(name = "device_id")
val deviceId: String
) {
#JsonClass(generateAdapter = true)
class ApplicationIds(
)
}
#JsonClass(generateAdapter = true)
data class UplinkMessage(
#Json(name = "decoded_payload")
val decodedPayload: DecodedPayload,
#Json(name = "received_at")
val receivedAt: String,
#Json(name = "settings")
val settings: Settings
) {
#JsonClass(generateAdapter = true)
data class DecodedPayload(
#Json(name = "brightness")
val brightness: Int
)
#JsonClass(generateAdapter = true)
data class Settings(
#Json(name = "data_rate")
val dataRate: DataRate
) {
#JsonClass(generateAdapter = true)
class DataRate(
)
}
}
}
}
#tyczj was right, this is not valid ndjson, as in ndjson newline characters only appear after each seperate json text. The solution was to send Accept: text/event-stream with the HTTP request and now I'm getting a valid ndjson response like this:
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:19:04.021238048Z","uplink_message":{"decoded_payload":{"brightness":0},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:19:03.809173924Z"}}}
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:25:22.260712161Z","uplink_message":{"decoded_payload":{"brightness":119},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:25:22.046086937Z"}}}
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:18:58.438947740Z","uplink_message":{"decoded_payload":{"brightness":0},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:18:58.228671174Z"}}}
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:21:10.102303310Z","uplink_message":{"decoded_payload":{"brightness":106},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:21:09.893217735Z"}}}
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:23:16.177064041Z","uplink_message":{"decoded_payload":{"brightness":108},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:23:15.967959055Z"}}}
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:27:28.334312076Z","uplink_message":{"decoded_payload":{"brightness":117},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:27:28.126104222Z"}}}
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:29:34.400253264Z","uplink_message":{"decoded_payload":{"brightness":99},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:29:34.190980301Z"}}}
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:31:40.481766225Z","uplink_message":{"decoded_payload":{"brightness":118},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:31:40.270452429Z"}}}
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:33:46.567235913Z","uplink_message":{"decoded_payload":{"brightness":114},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:33:46.357373037Z"}}}
{"result":{"end_device_ids":{"device_id":"esp32-bh1750","application_ids":{}},"received_at":"2021-04-24T10:35:52.737386496Z","uplink_message":{"decoded_payload":{"brightness":121},"settings":{"data_rate":{}},"received_at":"2021-04-24T10:35:52.426583804Z"}}}
I've added the Header via retrofit annotation:
#Headers("Accept: text/event-stream ")

How to parse JSON indexed dictionnary in Kotlin [duplicate]

I'm receiving a quite deep JSON object string from a service which I must parse to a JSON object and then map it to classes.
How can I transform a JSON string to object in Kotlin?
After that the mapping to the respective classes, I was using StdDeserializer from Jackson. The problem arises at the moment the object had properties that also had to be deserialized into classes. I was not able to get the object mapper, at least I didn't know how, inside another deserializer.
Preferably, natively, I'm trying to reduce the number of dependencies I need so if the answer is only for JSON manipulation and parsing it'd be enough.
There is no question that the future of parsing in Kotlin will be with kotlinx.serialization. It is part of Kotlin libraries. Version kotlinx.serialization 1.0 is finally released
https://github.com/Kotlin/kotlinx.serialization
import kotlinx.serialization.*
import kotlinx.serialization.json.JSON
#Serializable
data class MyModel(val a: Int, #Optional val b: String = "42")
fun main(args: Array<String>) {
// serializing objects
val jsonData = JSON.stringify(MyModel.serializer(), MyModel(42))
println(jsonData) // {"a": 42, "b": "42"}
// serializing lists
val jsonList = JSON.stringify(MyModel.serializer().list, listOf(MyModel(42)))
println(jsonList) // [{"a": 42, "b": "42"}]
// parsing data back
val obj = JSON.parse(MyModel.serializer(), """{"a":42}""")
println(obj) // MyModel(a=42, b="42")
}
You can use this library https://github.com/cbeust/klaxon
Klaxon is a lightweight library to parse JSON in Kotlin.
Without external library (on Android)
To parse this:
val jsonString = """
{
"type":"Foo",
"data":[
{
"id":1,
"title":"Hello"
},
{
"id":2,
"title":"World"
}
]
}
"""
Use these classes:
import org.json.JSONObject
class Response(json: String) : JSONObject(json) {
val type: String? = this.optString("type")
val data = this.optJSONArray("data")
?.let { 0.until(it.length()).map { i -> it.optJSONObject(i) } } // returns an array of JSONObject
?.map { Foo(it.toString()) } // transforms each JSONObject of the array into Foo
}
class Foo(json: String) : JSONObject(json) {
val id = this.optInt("id")
val title: String? = this.optString("title")
}
Usage:
val foos = Response(jsonString)
You can use Gson .
Example
Step 1
Add compile
compile 'com.google.code.gson:gson:2.8.2'
Step 2
Convert json to Kotlin Bean(use JsonToKotlinClass)
Like this
Json data
{
"timestamp": "2018-02-13 15:45:45",
"code": "OK",
"message": "user info",
"path": "/user/info",
"data": {
"userId": 8,
"avatar": "/uploads/image/20180115/1516009286213053126.jpeg",
"nickname": "",
"gender": 0,
"birthday": 1525968000000,
"age": 0,
"province": "",
"city": "",
"district": "",
"workStatus": "Student",
"userType": 0
},
"errorDetail": null
}
Kotlin Bean
class MineUserEntity {
data class MineUserInfo(
val timestamp: String,
val code: String,
val message: String,
val path: String,
val data: Data,
val errorDetail: Any
)
data class Data(
val userId: Int,
val avatar: String,
val nickname: String,
val gender: Int,
val birthday: Long,
val age: Int,
val province: String,
val city: String,
val district: String,
val workStatus: String,
val userType: Int
)
}
Step 3
Use Gson
var gson = Gson()
var mMineUserEntity = gson?.fromJson(response, MineUserEntity.MineUserInfo::class.java)
Not sure if this is what you need but this is how I did it.
Using import org.json.JSONObject :
val jsonObj = JSONObject(json.substring(json.indexOf("{"), json.lastIndexOf("}") + 1))
val foodJson = jsonObj.getJSONArray("Foods")
for (i in 0..foodJson!!.length() - 1) {
val categories = FoodCategoryObject()
val name = foodJson.getJSONObject(i).getString("FoodName")
categories.name = name
}
Here's a sample of the json :
{"Foods": [{"FoodName": "Apples","Weight": "110" } ]}
I personally use the Jackson module for Kotlin that you can find here: jackson-module-kotlin.
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$version"
As an example, here is the code to parse the JSON of the Path of Exile skilltree which is quite heavy (84k lines when formatted) :
Kotlin code:
package util
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.*
import java.io.File
data class SkillTreeData( val characterData: Map<String, CharacterData>, val groups: Map<String, Group>, val root: Root,
val nodes: List<Node>, val extraImages: Map<String, ExtraImage>, val min_x: Double,
val min_y: Double, val max_x: Double, val max_y: Double,
val assets: Map<String, Map<String, String>>, val constants: Constants, val imageRoot: String,
val skillSprites: SkillSprites, val imageZoomLevels: List<Int> )
data class CharacterData( val base_str: Int, val base_dex: Int, val base_int: Int )
data class Group( val x: Double, val y: Double, val oo: Map<String, Boolean>?, val n: List<Int> )
data class Root( val g: Int, val o: Int, val oidx: Int, val sa: Int, val da: Int, val ia: Int, val out: List<Int> )
data class Node( val id: Int, val icon: String, val ks: Boolean, val not: Boolean, val dn: String, val m: Boolean,
val isJewelSocket: Boolean, val isMultipleChoice: Boolean, val isMultipleChoiceOption: Boolean,
val passivePointsGranted: Int, val flavourText: List<String>?, val ascendancyName: String?,
val isAscendancyStart: Boolean?, val reminderText: List<String>?, val spc: List<Int>, val sd: List<String>,
val g: Int, val o: Int, val oidx: Int, val sa: Int, val da: Int, val ia: Int, val out: List<Int> )
data class ExtraImage( val x: Double, val y: Double, val image: String )
data class Constants( val classes: Map<String, Int>, val characterAttributes: Map<String, Int>,
val PSSCentreInnerRadius: Int )
data class SubSpriteCoords( val x: Int, val y: Int, val w: Int, val h: Int )
data class Sprite( val filename: String, val coords: Map<String, SubSpriteCoords> )
data class SkillSprites( val normalActive: List<Sprite>, val notableActive: List<Sprite>,
val keystoneActive: List<Sprite>, val normalInactive: List<Sprite>,
val notableInactive: List<Sprite>, val keystoneInactive: List<Sprite>,
val mastery: List<Sprite> )
private fun convert( jsonFile: File ) {
val mapper = jacksonObjectMapper()
mapper.configure( DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true )
val skillTreeData = mapper.readValue<SkillTreeData>( jsonFile )
println("Conversion finished !")
}
fun main( args : Array<String> ) {
val jsonFile: File = File( """rawSkilltree.json""" )
convert( jsonFile )
JSON (not-formatted): http://filebin.ca/3B3reNQf3KXJ/rawSkilltree.json
Given your description, I believe it matches your needs.
GSON is a good choice for Android and Web platform to parse JSON in a Kotlin project. This library is developed by Google.
https://github.com/google/gson
1. First, add GSON to your project:
dependencies {
implementation 'com.google.code.gson:gson:2.8.9'
}
2. Now you need to convert your JSON to Kotlin Data class:
Copy your JSON and go to this(https://json2kt.com) website and paste your JSON to Input Json box. Write package(ex: com.example.appName) and Class name(ex: UserData) in proper box. This site will show live preview of your data class below and also you can download all classes at once in a zip file.
After downloading all classes extract the zip file & place them into your project.
3. Now Parse like below:
val myJson = """
{
"user_name": "john123",
"email": "john#example.com",
"name": "John Doe"
}
""".trimIndent()
val gson = Gson()
var mUser = gson.fromJson(myJson, UserData::class.java)
println(mUser.userName)
Done :)
This uses kotlinx.serialization like Elisha's answer. Meanwhile the project is past version 1.0 so the API has changed. Note that e.g. JSON.parse was renamed to Json.decodeFromString. Also it is imported in gradle differently starting in Kotlin 1.4.0:
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.0"
}
apply plugin: 'kotlinx-serialization'
Example usage:
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
#Serializable
data class Point(val x: Int, val y: Int)
val pt = Json.decodeFromString<Point>("""{"y": 1, "x": 2}""")
val str = Json.encodeToString(pt) // type can be inferred!
val ilist = Json.decodeFromString<List<Int>>("[-1, -2]")
val ptlist = Json.decodeFromString<List<Point>>(
"""[{"x": 3, "y": 4}, {"x": 5, "y": 6}]"""
)
You can use nullable types (T?) for both nullable and optional fields:
#Serializable
data class Point2(val x: Int, val y: Int? = null)
val nlist = Json.decodeFromString<List<Point2>>(
"""[{"x": 7}, {"x": 8, "y": null}, {"x": 9, "y": 0}]"""
)
Kotlin's data class is a class that mainly holds data and has members, .toString() and other methods (e.g. destructuring declarations) automatically defined.
To convert JSON to Kotlin use http://www.json2kotlin.com/
Also you can use Android Studio plugin. File > Settings, select Plugins in left tree, press "Browse repositories...", search "JsonToKotlinClass", select it and click green button "Install".
After AS restart you can use it. You can create a class with File > New > JSON To Kotlin Class (JsonToKotlinClass). Another way is to press Alt + K.
Then you will see a dialog to paste JSON.
In 2018 I had to add package com.my.package_name at the beginning of a class.
First of all.
You can use JSON to Kotlin Data class converter plugin in Android Studio for JSON mapping to POJO classes (kotlin data class).
This plugin will annotate your Kotlin data class according to JSON.
Then you can use GSON converter to convert JSON to Kotlin.
Follow this Complete tutorial:
Kotlin Android JSON Parsing Tutorial
If you want to parse json manually.
val **sampleJson** = """
[
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio
reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita"
}]
"""
Code to Parse above JSON Array and its object at index 0.
var jsonArray = JSONArray(sampleJson)
for (jsonIndex in 0..(jsonArray.length() - 1)) {
Log.d("JSON", jsonArray.getJSONObject(jsonIndex).getString("title"))
}
Kotlin Serialization
Kotlin specific library by JetBrains for all supported platforms – Android, JVM, JavaScript, Native.
https://github.com/Kotlin/kotlinx.serialization
Moshi
Moshi is a JSON library for Android and Java by Square.
https://github.com/square/moshi
Jackson
https://github.com/FasterXML/jackson
Gson
Most popular but almost deprecated.
https://github.com/google/gson
JSON to Java
http://www.jsonschema2pojo.org/
JSON to Kotlin
IntelliJ plugin - https://plugins.jetbrains.com/plugin/9960-json-to-kotlin-class-jsontokotlinclass-
Parse JSON string to Kotlin object
As others recommend, Gson library is the simplest way!
If the File is in the Asset folder you can do like this, first add
dependencies {
implementation 'com.google.code.gson:gson:2.9.0'
}
then get a file from Asset:
jsonString = context.assets.open(fileName).bufferedReader().use { it.readText() }
then use Gson :
val gson = Gson()
val listPersonType = object : TypeToken<List<Person>>() {}.type
var persons: List<Person> = gson.fromJson(jsonFileString, listPersonType)
persons.forEachIndexed { idx, person -> Log.i("data", "> Item $idx:\n$person") }
Where Person is a Model/Data class, like this
data class Person(val name: String, val age: Int, val messages: List) {
}
If you prefer parsing JSON to JavaScript-like constructs making use of Kotlin syntax, I recommend JSONKraken, of which I am the author.
You can do things like:
val json: JsonValue = JsonKraken.deserialize("""{"getting":{"started":"Hello World"}}""")
println(JsonKraken.serialize(json)) //prints: {"getting":{"started":"Hello World"}}
println(json["getting"]["started"].cast<String>()) //prints: Hello World
Suggestions and opinions on the matter are much apreciated!
I created a simple Extention function to convert JSON string to model class
inline fun <reified T: Any> String.toKotlinObject(): T =
Gson().fromJson(this, T::class.java)
Usage method
stringJson.toKotlinObject<MyModelClass>()
http://www.jsonschema2pojo.org/
Hi you can use this website to convert json to pojo.
control+Alt+shift+k
After that you can manualy convert that model class to kotlin model class. with the help of above shortcut.
Seems like Kotlin does not have any built-in method as in many cases it just imports and implements some tools from Java. After trying lots of packages, finally this one worked reasonably. This fastjson from alibaba, which is very easy to use. Inside build gradle dependencies:
implementation 'com.alibaba:fastjson:1.1.67.android'
Inside your Kotlin code:
import com.alibaba.fastjson.JSON
var jsonDecodedMap: Map<String, String> =
JSON.parse(yourStringValueHere) as Map<String, String>;
Download the source of deme from here(Json parsing in android kotlin)
Add this dependency:
compile 'com.squareup.okhttp3:okhttp:3.8.1'
Call api function:
fun run(url: String) {
dialog.show()
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
dialog.dismiss()
}
override fun onResponse(call: Call, response: Response) {
var str_response = response.body()!!.string()
val json_contact:JSONObject = JSONObject(str_response)
var jsonarray_contacts:JSONArray= json_contact.getJSONArray("contacts")
var i:Int = 0
var size:Int = jsonarray_contacts.length()
al_details= ArrayList();
for (i in 0.. size-1) {
var json_objectdetail:JSONObject=jsonarray_contacts.getJSONObject(i)
var model:Model= Model();
model.id=json_objectdetail.getString("id")
model.name=json_objectdetail.getString("name")
model.email=json_objectdetail.getString("email")
model.address=json_objectdetail.getString("address")
model.gender=json_objectdetail.getString("gender")
al_details.add(model)
}
runOnUiThread {
//stuff that updates ui
val obj_adapter : CustomAdapter
obj_adapter = CustomAdapter(applicationContext,al_details)
lv_details.adapter=obj_adapter
}
dialog.dismiss()
}
})

Categories

Resources