Deserializing JSON with JSON4S - json

I have JSON in following format:
{
"id": 1913548255,
"notification": "NotificationReceived",
"deviceGuid": "e60d6085-2aba-48e9-b1c3-73c673e414be",
"timestamp": "2016-01-28T20:34:34.167",
"parameters": {
"jsonString": "{\"mac\":\"bc6a29abd973\",\"uuid\":\"f000aa1104514000b000000000000000\",\"value\":0.27328648477047685}"
}
}
I want to deserialize it to get following classes, so that :
case class Parameters(mac: String, uuid: String, value: Double)
case class Notification(id: BigInt, notification: String, deviceGuid: String, timestamp: String, perameters: Parameters)
I know i need to write CustomSerializer. But i don't have much experience. Please, guide me. Thanks for help.

I decided no to deal with deserializer, but do it in ordinary way. I am posting the code so that it may help someone.
case class Parameters(mac: String, uuid: String, value: Double)
case class Notification(id: Int, notification: String, deviceGuid: String, timestamp: String, parameters: Map[String, String])
case class FinalNotification(id: Int, notification: String, device_guid: String, timestamp: String, mac: String, uuid: String, value: Double)
implicit val formats = DefaultFormats
val n = parse(v).extract[Notification]
def convertJson(json: Option[String]): Parameters = json match {
case None => throw new IllegalArgumentException("Json can't be converted. ")
case Some(j) => parse(j).extract[Parameters]
}
val param = convertJson(n.parameters.get("jsonString"))
FinalNotification(n.id, n.notification, n.deviceGuid, n.timestamp, param.mac, param.uuid, param.value)

Related

JSON decode nested field as Map[String, String] in Scala using circe

A circe noob here. I am trying to decode a JSON string to case class in Scala using circe. I want one of the nested fields in the input JSON to be decoded as a Map[String, String] instead of creating a separate case class for it.
Sample code:
import io.circe.parser
import io.circe.generic.semiauto.deriveDecoder
case class Event(
action: String,
key: String,
attributes: Map[String, String],
session: String,
ts: Long
)
case class Parsed(
events: Seq[Event]
)
Decoder[Map[String, String]]
val jsonStr = """{
"events": [{
"ts": 1593474773,
"key": "abc",
"action": "hello",
"session": "def",
"attributes": {
"north_lat": -32.34375,
"south_lat": -33.75,
"west_long": -73.125,
"east_long": -70.3125
}
}]
}""".stripMargin
implicit val eventDecoder = deriveDecoder[Event]
implicit val payloadDecoder = deriveDecoder[Parsed]
val decodeResult = parser.decode[Parsed](jsonStr)
val res = decodeResult match {
case Right(staff) => staff
case Left(error) => error
}
I am ending up with a decoding error on attributes field as follows:
DecodingFailure(String, List(DownField(north_lat), DownField(attributes), DownArray, DownField(events)))
I found an interesting link here on how to decode JSON string to a map here: Convert Json to a Map[String, String]
But I'm having little luck as to how to go about it.
If someone can point me in the right direction or help me out on this that will be awesome.
Let's parse the error :
DecodingFailure(String, List(DownField(geotile_north_lat), DownField(attributes), DownArray, DownField(events)))
It means we should look in "events" for an array named "attributes", and in this a field named "geotile_north_lat". This final error is that this field couldn't be read as a String. And indeed, in the payload you provide, this field is not a String, it's a Double.
So your problem has nothing to do with Map decoding. Just use a Map[String, Double] and it should work.
So you can do something like this:
final case class Attribute(
key: String,
value: String
)
object Attribute {
implicit val attributesDecoder: Decoder[List[Attribute]] =
Decoder.instance { cursor =>
cursor
.value
.asObject
.toRight(
left = DecodingFailure(
message = "The attributes field was not an object",
ops = cursor.history
)
).map { obj =>
obj.toList.map {
case (key, value) =>
Attribute(key, value.toString)
}
}
}
}
final case class Event(
action: String,
key: String,
attributes: List[Attribute],
session: String,
ts: Long
)
object Event {
implicit val eventDecoder: Decoder[Event] = deriveDecoder
}
Which you can use like this:
val result = for {
json <- parser.parse(jsonStr).left.map(_.toString)
obj <- json.asObject.toRight(left = "The input json was not an object")
eventsRaw <- obj("events").toRight(left = "The input json did not have the events field")
events <- eventsRaw.as[List[Event]].left.map(_.toString)
} yield events
// result: Either[String, List[Event]] = Right(
// List(Event("hello", "abc", List(Attribute("north_lat", "-32.34375"), Attribute("south_lat", "-33.75"), Attribute("west_long", "-73.125"), Attribute("east_long", "-70.3125")), "def", 1593474773L))
// )
You can customize the Attribute class and its Decoder, so their values are Doubles or Jsons.

How to write POJOs to get specific array inside an object from JSON using Retrofit2?

I need to do a task: paging list of news.
To do it I took a sample from googlesample/architecthurecomponents/PagingWithNetworkSample and encounter with this question. Question is about code from Google sample to parse JSON file.
JSON url: https://www.reddit.com/r/androiddev/hot.json
POJO file:
#Entity(tableName = "posts",
indices = [Index(value = ["subreddit"], unique = false)])
data class RedditPost(
#PrimaryKey
#SerializedName("name")
val name: String,
#SerializedName("title")
val title: String,
#SerializedName("score")
val score: Int,
#SerializedName("author")
val author: String,
#SerializedName("subreddit") // this seems mutable but fine for a demo
#ColumnInfo(collate = ColumnInfo.NOCASE)
val subreddit: String,
#SerializedName("num_comments")
val num_comments: Int,
#SerializedName("created_utc")
val created: Long,
val thumbnail: String?,
val url: String?) {
// to be consistent w/ changing backend order, we need to keep a data like this
var indexInResponse: Int = -1
}
and this is an API interface:
interface RedditApi {
#GET("/r/{subreddit}/hot.json")
fun getTop(
#Path("subreddit") subreddit: String,
#Query("limit") limit: Int): Call<ListingResponse>
#GET("/r/{subreddit}/hot.json")
fun getTopAfter(
#Path("subreddit") subreddit: String,
#Query("after") after: String,
#Query("limit") limit: Int): Call<ListingResponse>
#GET("/r/{subreddit}/hot.json")
fun getTopBefore(
#Path("subreddit") subreddit: String,
#Query("before") before: String,
#Query("limit") limit: Int): Call<ListingResponse>
class ListingResponse(val data: ListingData)
class ListingData(
val children: List<RedditChildrenResponse>,
val after: String?,
val before: String?
)
data class RedditChildrenResponse(val data: RedditPost)
companion object {
private const val BASE_URL = "https://www.reddit.com/"
fun create(): RedditApi = create(HttpUrl.parse(BASE_URL)!!)
fun create(httpUrl: HttpUrl): RedditApi {
val logger = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger {
Log.d("API", it)
})
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.baseUrl(httpUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RedditApi::class.java)
}
}
}
The question is: how does the API request exactly finds what we need, a children: [...], which represent a list of posts? Because a children: [...] resides inside object and in code we don't have a POJO with #Serialized("children")field. Only a pojo for items inside children: [...]. I tried to implement this approach specific to my json, but it returns a null value.
Thanks everyone for help.
You don't have to add #SerializedName annotation if the name of the field in POJO is the same as the name of the field in JSON. That's why class ListingResponse(val data: ListingData) can be mapped to
{
"kind": "Listing",
"data": ...
}

How to get a nested object in JSON in Kotlin?

I've created the classes of Eqs and Service, got the service objects but can't get the list of eqs. Can anyone help me with this?
This the Eqs class
data class Eqs(
val name: String,
val imageUrl: String,
val description: String?,
val responsible: String
)
That's the Service class which gets its values
data class Service(
val title: String,
val servings: Int,
val eqs: List<Eqs>
) {
companion object {
fun getServicesFromFile(filename: String, context: Context): ArrayList<Service> {
val serviceList = ArrayList<Service>()
try {
// Load data
val jsonString = loadJsonFromAsset("services.json", context)
val json = JSONObject(jsonString)
val services = json.getJSONArray("services")
(0 until services.length()).mapTo(serviceList) {
Service(services.getJSONObject(it).getString("title"),
services.getJSONObject(it).getInt("servings"),
}
} catch (e: JSONException) {
e.printStackTrace()
}
return serviceList
}
I can't get the List of Eqs in my getServicesFromFile function. How to parse and get it correctly?
I recommend you to use Jackson library. It's simple and saves you a lot of time. You can find it's documentation here: https://www.baeldung.com/jackson-kotlin
You also can use some websites to generate the data class needed for Jackson like https://app.quicktype.io/
Use Json to Kotlin plugin
In tool bar of android studio Code >> Generate and copy & paste you API into it and give the class name
[
{
"id": 1,
"name" : "Madoldoowa",
"description": "Madol Doova (මඩොල් දූව) is a children's novel and coming-of-age story written by Sri Lankan writer
Martin Wickramasinghe and first published in 1947",
"language" : "Sinhala",
"isbn" : "ISBN232673434",
"file_size" : 300,
"no_of_pages" : 500,
"price" : 970,
"ratings" : "5.1K",
"cover_page" : "https://upload.wikimedia.org/wikipedia/en/5/5c/MadolDoova.jpg",
"author" : {
"name" : "Martin Wickramasinghe"
}
]
data class Model(
val author: Author,
val cover_page: String,
val description: String,
val file_size: Int,
val id: Int,
val isbn: String,
val language: String,
val name: String,
val no_of_pages: Int,
val price: Int,
val ratings: String
)
data class Author(
val name: String
)

Map JSON to nested case class play framework

I need to receive Json data and bind it to a case class that contains other case class in parameters.
I don't even know what to write in the controller, there's no help in the documentation neither, I've look everywhere else and didn't find any answer.
Here are the case classes:
case class InfoForm(nomEntreprise: String, siren: String, dateCreation: String, entreeRelation: String, secteur: String, cotationBDF: String, montantPrivileges: String, fcc: String, ca: String, resultatBrut: String, ebe: String, totalBilan: String, fp: String)
object InfoForm {
implicit val format = Json.format[InfoForm]
}
case class Associate(tiersAssoc: String, nom: String, prenom: String, birthday: Date)
object Associate {
implicit val assocFormat = Json.format[Associate]
}
case class AssociateForm(nbAssoc: Int, cotation: String, assoc: Seq[Associate], fccAssociate: String, ficp: String)
object AssociateForm {
implicit val format = Json.format[AssociateForm]
}
case class OperationForm(mntAcquisition: String, codePostal: String, typePret: String, mntFinance: String, mensualite: String, loyerPrevisionnel: String)
object OperationForm {
implicit val format = Json.format[OperationForm]
}
case class CompanyForm(sci: Boolean, infoForm: InfoForm, associateForm: AssociateForm, operationForm: OperationForm)
object CompanyForm {
implicit val format = Json.format[CompanyForm]
}
Json:
{
"sci": true,
"nomEntreprise": "nom entreprise",
"siren": "siren",
"dateCreation": "1977-04-22T01:00:00-05:00",
"entreeRelation": "1977-04-22T01:00:00-05:00",
"secteur": "un secteur",
"cotationBDF": "cotation",
"montantPrivileges": "montant",
"fcc": "fcc",
"ca": "ca c est un option attention",
"resultatBrut": "resultat",
"ebe": "ebe",
"totalBilan": "totalBilan",
"fp": "fp",
"nbAssoc": 1,
"cotation": "une chaine",
"assoc": [
{
"tiersAssoc": "une chaine",
"nom": "name",
"prenom": "prenom",
"birthday":"1977-04-22T01:00:00-05:00"
}
],
"fccAssociate": "une autre chaine",
"ficp": "encore une autre chaine",
"mntAcquisition": "montant acquisition",
"codePostal": "code postal",
"typePret": "typePret",
"mntFinance": "montant finance",
"mensualite": "string",
"loyerPrevisionnel": "derniere !"
}
And here's what I've tried so far in the controller :
def setCompanyForm(id: String) = {
Errors.collect {
silhouette.SecuredAction.async { implicit request =>
Errors.handle {
val companyForm = request.body.asJson
companyForm match {
case Some(json) => println(Json.fromJson[CompanyForm](json))
case None => println("rien")
}
Future.successful(Ok(""))
}
}
}
}
There is absolutely no log when I print.
According to the title I try to help you with the Json Mapping:
I created a ScalaFiddle, so you can try it yourself.
That I could start I replaced the Date with a simple String, because you missed to have a formatter for Date (and it is not clear what Date you have).
So running this is not a success, because of the wrong Type:
Json.parse(json).validate[CompanyForm]
After fixing that everything works as expected: ScalaFiddle v.2
Json.parse(json).validate[InfoForm]

Circe Unmarshall HttpResponse

I'm trying to ask consul for healthy services.The response is:
HttpResponse(200 OK,List(X-Consul-Index: 3471242, X-Consul-Knownleader: true, X-Consul-Lastcontact: 0, Date: Fri, 02 Mar 2018 16:06:08 GMT),HttpEntity.Strict(application/json,[{"Node":{"Node":"ci-content-1","Address":"10.45.200.14","TaggedAddresses":{"wan":"10.45.200.14"},"CreateIndex":2708577,"ModifyIndex":3470978},"Service":{"ID":"de62bdcb8e37:varnish_2:6085","Service":"shop-varnish-6085","Tags":null,"Address":"10.45.200.14","Port":33889,"EnableTagOverride":false,"CreateIndex":3313055,"ModifyIndex":3313055},"Checks":[{"Node":"ci-content-1","CheckID":"serfHealth","Name":"Serf Health Status","Status":"passing","Notes":"","Output":"Agent alive and reachable","ServiceID":"","ServiceName":"","CreateIndex":2708577,"ModifyIndex":3451134}]},{"Node":{"Node":"ci-content-2","Address":"10.45.200.18","TaggedAddresses":{"wan":"10.45.200.18"},"CreateIndex":2158463,"ModifyIndex":3471241},"Service":{"ID":"f89a94600d4c:varnish_1:6085","Service":"shop-varnish-6085","Tags":null,"Address":"10.45.200.18","Port":33622,"EnableTagOverride":false,"CreateIndex":3313064,"ModifyIndex":3313064},"Checks":[{"Node":"toom-ci-content-2","CheckID":"serfHealth","Name":"Serf Health Status","Status":"passing","Notes":"","Output":"Agent alive and reachable","ServiceID":"","ServiceName":"","CreateIndex":2158464,"ModifyIndex":3297480}]}]
The class definitions are:
final case class TaggedAddresses (
wan: String)
final case class Node (
node: String,
address: String,
taggedAddresses: TaggedAddresses,
createIndex: Int,
modifyIndex: Int
)
final case class Service (
id: String,
service: String,
tags: String,
addresses: String,
port: Int,
enableTagOverride: String,
createIndex: Int,
modifyIndex: Int
)
final case class Check (
node: String,
checkId:String,
name: String,
status: String,
notes: String,
output: String,
serviceId: String,
serviceName:String,
createIndex: Int,
modifyIndex: Int
)
final case class NodeInfo(
node: Node,
service: Service,
checkList: List[Check]
)
package object VarnishInformation {}
Then I try to unmarshall:
val request = HttpRequest(method = HttpMethods.GET, uri = consulUrl)
val response = Await.result(Http().singleRequest(request), 10.seconds)
log.info("Entity: " + response.httpMessage)
val entries = Unmarshal(response).to[List[NodeInfo]]
and get the following error:
Error during processing of request: 'Attempt to decode value on failed cursor: DownField(node),DownArray'. Completing with 500 Internal Server Error response. To change default exception handling behavior, provide a custom ExceptionHandler.
Ican't see the failure, anybody else can do?
The short answer: Assuming you have all the required decoders and encoders in place, you should simply fix your case class as follows:
case class Node (
Node: String,
Address: String,
TaggedAddresses: TaggedAddresses,
CreateIndex: Int,
ModifyIndex: Int
)
I.e., you have to use the tag names exactly as they appear in your JSON.
The long answer: if I'm taking out the relevant JSON from your object as follows:
val jsonString =
"""
{
"Node":{
"Node":"ci-content-1",
"Address":"10.45.200.14",
"TaggedAddresses":{
"wan":"10.45.200.14"
},
"CreateIndex":2708577,
"ModifyIndex":3470978
},
...
"""
Then the following code will yield Right(Node(ci-content-1,10.45.200.14,TaggedAddresses(10.45.200.14),2708577,3470978)), when the above, corrected version of the case class is used:
def myParse(jsonString: String) = {
val res = parse(jsonString) match {
case Right(json) => {
val cursor = json.hcursor
cursor.get[Node]("Node")
}
case _ => Left("Wrong JSON!")
}
println(res)
}
Otherwise, I also get the same error you described.