Parse Json with Kotlin - json

I have the following json to parse:
{
"id":"123",
"nbElements":15,
"containers":[
{
"id":"cont1",
"capacity":3
}
],
"operations":[
{
"id":"cont1_01",
"weight":3,
"containerId":"cont1"
},
{
"id":"cont1_02",
"weight": 4,
"containerId":"cont1"
}
]
}
Using quicktype, it suggests the following code:
import com.fasterxml.jackson.annotation.*
import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.*
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.module.kotlin.*
val mapper = jacksonObjectMapper().apply {
propertyNamingStrategy = PropertyNamingStrategy.LOWER_CAMEL_CASE
setSerializationInclusion(JsonInclude.Include.NON_NULL)
}
data class Welcome (
#get:JsonProperty(required=true)#field:JsonProperty(required=true)
val id: String,
#get:JsonProperty(required=true)#field:JsonProperty(required=true)
val nbElements: Long,
#get:JsonProperty(required=true)#field:JsonProperty(required=true)
val containers: List<Container>,
#get:JsonProperty(required=true)#field:JsonProperty(required=true)
val items: List<Item>
) {
fun toJson() = mapper.writeValueAsString(this)
companion object {
fun fromJson(json: String) = mapper.readValue<Welcome>(json)
}
}
data class Container (
#get:JsonProperty(required=true)#field:JsonProperty(required=true)
val id: String,
#get:JsonProperty(required=true)#field:JsonProperty(required=true)
val capacity: Long
)
data class Item (
#get:JsonProperty(required=true)#field:JsonProperty(required=true)
val id: String,
#get:JsonProperty(required=true)#field:JsonProperty(required=true)
val weight: Long,
#get:JsonProperty("containerId", required=true)#field:JsonProperty("containerId", required=true)
val containerID: String
)
I want to store the whole container object in each item instead of the containerId but I don't want to change the json, it must be done in the code.
More generally, is there way to make the code more concise? Is there more suitable tool than jackson for example.

Related

How to parse this such json in Kotlin with GSON?

I have JSON object like this
{
"codemap":{
"codeOfItem1":"titleOfItem1",
"codeOfItem2":"titleOfItem2",
"codeOfItem3":"titleOfItem3",
"codeOfItem4":"titleOfItem4"
},
"items":{
"titleOfItem1":{
"attribute1":"value1",
"atttribute2":{
"subattr1":"value1_of_subattr1_for_item1",
"subattr2":"value1_of_subattr2_for_item1"
}
},
"titleOfItem2":{
"attribute1":"value2",
"atttribute2":{
"subattr1":"value1_of_subattr1_for_item2",
"subattr2":"value1_of_subattr2_for_item2"
}
},
"titleOfItem3":{
"attribute1":"value2",
"atttribute2":{
"subattr1":"value1_of_subattr1_for_item3",
"subattr2":"value1_of_subattr2_for_item3"
}
},
"titleOfItem4":{
"attribute1":"value2",
"atttribute2":{
"subattr1":"value1_of_subattr1_for_item4",
"subattr2":"value1_of_subattr2_for_item4"
}
}
}
}
How to parse it using GSON in Kotlin ?
(Problem is that strings like titleOfItemXXX is both values in codemap map and key names in items map
I don't really like idea to go fully manual way like in How to parse this Json with no object name
Update:
I don't want to get scheme like this (this is from Kotlin-to-JSON Android Studio Plugin)
import com.google.gson.annotations.SerializedName
data class x1(
#SerializedName("codemap")
val codemap: Codemap,
#SerializedName("items")
val items: Items
) {
data class Codemap(
#SerializedName("codeOfItem1")
val codeOfItem1: String, // titleOfItem1
#SerializedName("codeOfItem2")
val codeOfItem2: String, // titleOfItem2
#SerializedName("codeOfItem3")
val codeOfItem3: String, // titleOfItem3
#SerializedName("codeOfItem4")
val codeOfItem4: String // titleOfItem4
)
data class Items(
#SerializedName("titleOfItem1")
val titleOfItem1: TitleOfItem1,
#SerializedName("titleOfItem2")
val titleOfItem2: TitleOfItem2,
#SerializedName("titleOfItem3")
val titleOfItem3: TitleOfItem3,
#SerializedName("titleOfItem4")
val titleOfItem4: TitleOfItem4
) {
data class TitleOfItem1(
#SerializedName("attribute1")
val attribute1: String, // value1
#SerializedName("atttribute2")
val atttribute2: Atttribute2
) {
data class Atttribute2(
#SerializedName("subattr1")
val subattr1: String, // value1_of_subattr1_for_item1
#SerializedName("subattr2")
val subattr2: String // value1_of_subattr2_for_item1
)
}
data class TitleOfItem2(
#SerializedName("attribute1")
val attribute1: String, // value2
#SerializedName("atttribute2")
val atttribute2: Atttribute2
) {
data class Atttribute2(
#SerializedName("subattr1")
val subattr1: String, // value1_of_subattr1_for_item2
#SerializedName("subattr2")
val subattr2: String // value1_of_subattr2_for_item2
)
}
data class TitleOfItem3(
#SerializedName("attribute1")
val attribute1: String, // value2
#SerializedName("atttribute2")
val atttribute2: Atttribute2
) {
data class Atttribute2(
#SerializedName("subattr1")
val subattr1: String, // value1_of_subattr1_for_item3
#SerializedName("subattr2")
val subattr2: String // value1_of_subattr2_for_item3
)
}
data class TitleOfItem4(
#SerializedName("attribute1")
val attribute1: String, // value2
#SerializedName("atttribute2")
val atttribute2: Atttribute2
) {
data class Atttribute2(
#SerializedName("subattr1")
val subattr1: String, // value1_of_subattr1_for_item4
#SerializedName("subattr2")
val subattr2: String // value1_of_subattr2_for_item4
)
}
}
}
because I don't really known how much items I will have and which names they will use in production.
I think you want dynamic-named property in the schema. In this case, declare it as Map:
data class Schema(val codemap: Map<String, String>, val items: Map<String, Item>) {
data class Item(val attribute1: String, val atttribute2: Attr2) {
data class Attr2(val subattr1: String, val subattr2: String)
}
}
fun gsonDemo() {
val json = """
{
"codemap":{
"codeOfItem1":"titleOfItem1",
"codeOfItem2":"titleOfItem2",
"codeOfItem3":"titleOfItem3",
"codeOfItem4":"titleOfItem4"
},
"items":{
"titleOfItem1":{
"attribute1":"value1",
"atttribute2":{
"subattr1":"value1_of_subattr1_for_item1",
"subattr2":"value1_of_subattr2_for_item1"
}
},
"titleOfItem2":{
"attribute1":"value2",
"atttribute2":{
"subattr1":"value1_of_subattr1_for_item2",
"subattr2":"value1_of_subattr2_for_item2"
}
},
"titleOfItem3":{
"attribute1":"value2",
"atttribute2":{
"subattr1":"value1_of_subattr1_for_item3",
"subattr2":"value1_of_subattr2_for_item3"
}
},
"titleOfItem4":{
"attribute1":"value2",
"atttribute2":{
"subattr1":"value1_of_subattr1_for_item4",
"subattr2":"value1_of_subattr2_for_item4"
}
}
}
}
""".trimIndent()
val obj = Gson().fromJson(json, Schema::class.java)
println(obj.items[obj.codemap["codeOfItem3"]]?.atttribute2?.subattr1) // print value1_of_subattr1_for_item3
}
Note that Gson won't fail if some properties are missing, so all the values of properties may be null, although they are declared as non-nullable
Looks like I forget about simple ways :(
Working answer:
data class TopLevel (
#SerializedName("codemap")
val codemap: Map<String, String>,
#SerializedName("items")
val items: Map<String, Item>
)
data class Item (
#SerializedName("attribute1")
val attribute1: Attribute1,
#SerializedName("attribute2")
val attribute2: Attribute2
)
data class Attribute2 (
#SerializedName("subattr1")
val subattr1: String,
#SerializedName("subattr2")
val subattr1: String
)
enum class Attribute1 {
#SerializedName("DarkSide")
DarkSide,
#SerializedName("LightSide")
LightSide
}
var gson: Gson = Gson()
val str=... //string with source JSON
val result = gson.fromJson(str, TopLeve::class.java)
Everything appears to work correctly.

How to use #Serializer on deeper JSON datastructures

I'm new in Kotlin as a PHP dev. I have a data model, something like this:
#Serializable
data class Site (
#SerialName("id")
val id: Int,
#SerialName("name")
val name: String,
#SerialName("accountId")
val accountId: Int,
}
I have JSON output something like the following, which comes from a external API and which I am unable to control:
{
"sites": {
"count": 1,
"site": [
{
"id": 12345,
"name": "Foobar",
"accountId": 123456
}
]
}
}
When trying to get this from the API with ktor HTTPClient, I'd like to instruct the serializer to use sites.site as the root for my Site datamodel. Currently, I get the error: Uncaught Kotlin exception: io.ktor.serialization.JsonConvertException: Illegal input and Caused by: kotlinx.serialization.json.internal.JsonDecodingException: Expected start of the array '[', but had 'EOF' instead at path: $
I'm using the following to fetch the endpoint:
package com.example.myapplication.myapp
import com.example.myapplication.myapp.models.Site
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
class Api {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
private val apiKey = "REDACTED"
private val installationId = "REDACTED"
private val apiHost = "REDACTED"
suspend fun getSitesList(): List<Site> {
return get("sites/list").body()
}
suspend fun get(endpoint: String): HttpResponse {
val response = client.get(buildEndpointUrl(endpoint))
return response
}
private fun buildEndpointUrl(endpoint: String): HttpRequestBuilder {
val builder = HttpRequestBuilder()
val parametersBuilder = ParametersBuilder()
parametersBuilder.append("api_key", apiKey)
builder.url {
protocol = URLProtocol.HTTPS
host = apiHost
encodedPath = endpoint
encodedParameters = parametersBuilder
}
builder.header("Accept", "application/json")
return builder
}
}
You have to model the whole response object and cannot just provide a model for some of its parts.
#Serializable
data class SitesResponse(
val sites: SitesContainer,
)
#Serializable
data class SitesContainer(
val count: Int,
val site: List<Site>,
)
#Serializable
data class Site(
val accountId: Int,
val id: Int,
val name: String,
)
you can try make your data model like this,
data class Site(
#SerializedName("sites")
var sites: Sites) {
data class Sites(
#SerializedName("count")
var count: Int,
#SerializedName("site")
var site: List<Site>
) {
data class Site(
#SerializedName("accountId")
var accountId: Int,
#SerializedName("id")
var id: Int,
#SerializedName("name")
var name: String
)
}}

Is there a way to deserialize Json into Kotlin data class and convert property types?

I have some json
{
"name": "Foo",
"age": 12,
"id": "1234567890"
}
Currently, my data class I want to deserialize into looks like this
data class Example( val name: String, val age: Int, val id: String)
Is there a way to simply have the data class as
data class Example( val name: String, val age: Int, val id: Long)
Attention to the id type Long
I suppose this can be achieved through the use of an extension function that parses the data class into a separate data classes.
But I'd like to know if this is possible in any other way. I'm using Jackson for deserialization.
You don't have to do anything. Jackson automatically converts the String into a Long if it is possible:
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
data class C(
val s: String,
val n: Long
)
fun main() {
val json = """{"s":"My string","n":"1234567890"}"""
val mapper = jacksonObjectMapper()
val c: C = mapper.readValue(json)
println(c) // prints "C(s=My string, n=1234567890)"
}
This automatic conversion is controlled by MapperFeature.ALLOW_COERCION_OF_SCALARS, which is enabled by default.

How to deserialize complex JSON with Scala SprayJSON?

I have the following JSON:
{
"key1":[
{
"key2":{
"key3":"value3",
"key4":"value4"
},
"key5":{
"key6":"value6"
},
"key7":[
{
"key8":"value8",
"key9":"value9"
}
],
"key10":"value10",
"key11":"value11"
}
],
"key12":"value12"
}
How can I retrieve nested elements (e.g. value6) using SprayJson.
I managed to only retrieve the top level key "key1".
case class Key1(key1: JsArray)
object Key1Protocol extends DefaultJsonProtocol {
implicit val key1: RootJsonFormat[Key1] = jsonFormat1(Key1)
}
<jsonString>.parseJson.convertTo[Key1]
This can help:
case class Key1(key1: JsArray)
object Key1Protocol extends DefaultJsonProtocol {
implicit val key1: RootJsonFormat[Key1] = jsonFormat1(Key1)
}
import spray.json._
import DefaultJsonProtocol._
object MainJson {
import Key1Protocol._
def main(args: Array[String]): Unit = {
val jsonAst = TestComplexJson.str.parseJson.convertTo[Key1]
val result = jsonAst.key1
.elements(0)
.asJsObject
.getFields("key5")(0)
.asJsObject()
.getFields("key6")(0)
println(result)
}
}
gives "value6"

How to edit existing JSON object with sprayJSON

I am using akka with spray json support for which I need to edit value in the recieved json.
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json._
final case class Item(name: String, id: Long)
final case class Order(items: List[Item],orderTag:String)
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val itemFormat = jsonFormat2(Item)
implicit val orderFormat = jsonFormat2(Order)
}
In my use case I recieve the json with orderTag value as null, all I need to do is edit the orderTag value with and then use it as entity value.Is it possible to write/edit jsonObject and How to do that ?
class MyJsonService extends Directives with JsonSupport {
// format: OFF
val route =
get {
pathSingleSlash {
complete(Item("thing", 42)) // will render as JSON
}
} ~
post {
entity(as[Order]) { order => // will unmarshal JSON to Order
val itemsCount = order.items.size
val itemNames = order.items.map(_.name).mkString(", ")
complete(s"Ordered $itemsCount items: $itemNames")
}
}
}
You can just edit the json AST like ..
val json = """{"orderTag":null}"""
val jsVal = json.parseJson
val updatedJs = if (jsObj.fields.get("orderTag") == Some(JsNull)) {
JsObject(jsObj.fields + ("orderTag" -> JsString("new tag")))
} else {
jsObj
}
updatedJs.compactPrint
res26: String = """
{"orderTag":"new tag"}
"""