Map JSON to nested case class play framework - json

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]

Related

How to create a List from a HTTP response string based on certain conditions?

I am trying to parse the result of a HTTPS request in Scala.
The HTTPS response is a String as follows:
{
"rows":
[
{
"log_forwarding_ip":"",
"device_name":"AD1",
"id":"51",
"mgmt_ip_addr":"192.168.25.150",
"log_forwarding":"1",
"isActive":"0"
},
{
"log_forwarding_ip":"192.168.1.1",
"device_name":"WIN-SRV2019",
"id":"50",
"mgmt_ip_addr":"192.168.25.151",
"log_forwarding":"1",
"isActive":"1"
},
{
"log_forwarding_ip":"129.168.1.2",
"device_name":"PA",
"id":"3",
"mgmt_ip_addr":"192.168.1.161",
"log_forwarding":"1",
"isActive":"1"
}
],
"status":1
}
I have to create a List of all id's where isActive and log_forwarding are both equal to 1.
So far what I have done is:
object syncTables {
def main(args: Array[String]): Unit = {
case class deviceInfo(log_forwarding_ip: String, device_name: String, id: String,
mgmt_ip_addr: String, log_forwarding: String, isActive: String)
try {
val r = requests.get("https://192.168.1.253/api/device/deviceinfo.php", verifySslCerts = false)
if (r.statusCode == 200) {
val x = r.text
println(x)
} else {
println("Error in API call: "+r.statusCode)
}
}
}
}
Now I'm really confused what to do next to achieve my result. I'm totally new to JSON, that's why I don't know which JSON library I should use.
I tried using Play Framework but it seems all pretty complicated to me.
Does Scala offer something like Python's json module where this task can be easily done by using dictionaries and lists.
I'm using Scala 2.11.12 and com.lihaoyi.requests.Any kind of help will be highly appreciated.Thanks in advance.
Use json4s to parse the JSON string. Let's call your JSON input string json, then you can do something like this
import org.json4s._
import org.json4s.jackson.JsonMethods._
case class DeviceInfo(log_forwarding_ip: String, device_name: String, id: String,
mgmt_ip_addr: String, log_forwarding: String, isActive: String)
implicit val formats: Formats = DefaultFormats // Brings in default date formats etc.
val parsedJson = parse(json)
val deviceInfos = parsedJson match {
case JObject(head :: _) =>
head._2
.extract[List[DeviceInfo]]
.filter(_.isActive == 1 && _.log_forwarding == 1)
}
This will output
val res0: List[DeviceInfo] = List(DeviceInfo("192.168.1.1","WIN-SRV2019","50","192.168.25.151","1","1"),DeviceInfo("129.168.1.2","PA","3","192.168.1.161","1","1"))

No instance of play.api.libs.json.Format is available for scala.Option[java.sql.Timestamp] in the implicit scope

I am completely new to scala and play.api.libs.json.Format.
Here is my code, I don't know where I am doing wrong.
import java.sql.Timestamp
import shapeless._
import slickless._
import play.api.libs.json._
case class AnnotationDto(
sequence: Int,
tagName: String,
value: String,
) {
require(!tagName.isEmpty, "annotation tagName must not be empty")
}
case class CommentDto(
commentText: String,
) {
require(!commentText.isEmpty, "commentText must not be empty")
}
case class LoanConditionDto(
conditionGuid: String,
sourceSystemId: String,
sourceType: String,
name: String,
descriptionTemplate: String,
description: String,
status: String,
priorTo: String,
owner: String,
category: String,
borrowerFacingName: Option[String] = None,
borrowerFacingDescriptionTemplate: Option[String] = None,
borrowerFacingDescription: Option[String] = None,
encompassGuid: Option[String] = None,
conditionStatusDateTime: Option[java.sql.Timestamp] = None,
ignoreRecoveryFlag: Option[Boolean] = Some(false),
annotations: Option[List[AnnotationDto]] = None,
comments: Option[List[CommentDto]] = None,
) {
require(!conditionGuid.trim.isEmpty, "conditionGuid must not be empty")
require(!sourceType.trim.isEmpty, "condition sourceType must not be empty")
require(!name.trim.isEmpty, "condition name must not be empty")
require(!description.trim.isEmpty, "condition description must not be empty")
require(!status.trim.isEmpty, "condition status must not be empty")
require(!priorTo.trim.isEmpty, "condition priorTo must not be empty")
require(!owner.trim.isEmpty, "condition owner must not be empty")
require(!category.trim.isEmpty, "condition category must not be empty")
}
case class LoanConditionUpdateDto (
conditionGuid: String,
encompassGuid: Option[String] = None,
conditionStatusDateTime: Option[java.sql.Timestamp] = None,
ignoreRecoveryFlag: Option[Boolean] = Some(false),
) {
require(!conditionGuid.trim.isEmpty, "conditionGuid must not be empty")
}
case class LoanConditionsDto(loanNumber: Option[String] = None, conditions: List[LoanConditionDto], userId: String)
case class LoanConditionsUpdateDto(loanNumber: Option[String] = None, conditions: List[LoanConditionUpdateDto], userId: String)
object AnnotationDto {
implicit val annotationFormat = Json.format[AnnotationDto]
}
object CommentDto {
implicit val commentFormat = Json.format[CommentDto]
}
object LoanConditionDto {
implicit val loanConditionFormat = Json.format[LoanConditionDto]
}
object LoanConditionUpdateDto {
implicit val loanConditionUpdateFormat = Json.format[LoanConditionUpdateDto]
}
object LoanConditionsDto {
implicit val loanConditionsFormat = Json.format[LoanConditionsDto]
}
object LoanConditionsUpdateDto {
implicit val loanConditionsUpdateFormat = Json.format[LoanConditionsUpdateDto]
}
I'm trying to serialize an item object into a JSON String. I receive the error a this place
object LoanConditionDto {
implicit val loanConditionFormat = Json.format[LoanConditionDto]
}
Error:
No instance of play.api.libs.json.Format is available for scala.Option[java.sql.Timestamp] in the implicit scope (Hint: if declared in the same file, make sure it's declared before)
Can anyone please help me to fix the error.
Thanks in advance
As correctly was suggested in comment section you need to define Format or Writes and Reads for JSON for java.sql.Timestamp, for instance render it as long value representing unix time.
You can do it like:
import play.api.libs.json._
import java.sql.Timestamp
import play.api.libs.functional.syntax._
object LoanConditionDto {
implicit val timestampReads: Reads[Timestamp] = {
implicitly[Reads[Long]].map(new Timestamp(_))
}
implicit val timestampWrites: Writes[Timestamp] = {
implicitly[Writes[Long]].contramap(_.getTime)
}
implicit val loanConditionFormat = Json.format[LoanConditionDto]
}
Scatie working example: https://scastie.scala-lang.org/Ko07Oa8iScW0i4vzE76K7w

spray json in scala: deserializing json with unknown fields without losing them

I'm looking for a solution to this but for json spray and my searches and attempts to get this working with json spray have failed thus far.
If I have the following json:
{
"personalData": {
"person": {
"first": "first_name",
"last": "last_name"
},
"contact": {
"phone": "1111111",
"address": {
"line": "123 Main St",
"city": "New York"
}
},
"xx": "yy", // unknown in advanced
"zz": { // unknown in advanced
"aa": "aa",
"bb": "bb",
"cc": {
"dd": "dd",
"ee": "ee"
}
}
}
}
We know for sure that the json will contain person and contact but we don't know what other fields may be created upstream from us that we don't care about/use.
I want to serialize this JSON into a case class containing person and contact but on the other hand, I don't want to lose the other fields (save them in a map so the class will be deserialized to the same json as received).
This is how far I've made it:
case class Address(
line: String,
city: String,
postalCode: Option[String]
)
case class Contact(
phone: String,
address: Address
)
case class Person(
first: String,
last: String
)
case class PersonalData(
person: Person,
contact: Contact,
extra: Map[String, JsValue]
)
implicit val personFormat = jsonFormat2(Person)
implicit val addressFormat = jsonFormat3(Address)
implicit val contactFormat = jsonFormat2(Contact)
implicit val personalDataFormat = new RootJsonFormat[PersonalData] {
def write(personalData: PersonalData): JsValue = {
JsObject(
"person" -> personalData.person.toJson,
"contact" -> personalData.contact.toJson,
// NOT SURE HOW TO REPRESENT extra input
)
}
def read(value: JsValue): CAERequestBEP = ???
}
Can someone help me do this with spray.json instead of play? I've spent such a long time trying to do this and can't seem to make it work.
In order to do that, you need to write your own formatter for PersonalDataFormat:
case class Person(first: String, last: String)
case class Address(line: String, city: String)
case class Contact(phone: String, address: Address)
case class PersonalData(person: Person, contact: Contact, extra: Map[String, JsValue])
case class Entity(personalData: PersonalData)
implicit val personFormat = jsonFormat2(Person)
implicit val addressFormat = jsonFormat2(Address)
implicit val contactFormat = jsonFormat2(Contact)
implicit object PersonalDataFormat extends RootJsonFormat[PersonalData] {
override def read(json: JsValue): PersonalData = {
val fields = json.asJsObject.fields
val person = fields.get("person").map(_.convertTo[Person]).getOrElse(???) // Do error handling instead of ???
val contact = fields.get("contact").map(_.convertTo[Contact]).getOrElse(???) // Do error handling instead of ???
PersonalData(person, contact, fields - "person" - "contact")
}
override def write(personalData: PersonalData): JsValue = {
JsObject(personalData.extra ++ ("person" -> personalData.person.toJson, "contact" -> personalData.contact.toJson))
}
}
implicit val entityFormat = jsonFormat1(Entity)
val jsonResult = jsonString.parseJson.convertTo[Entity]
The result is:
Entity(PersonalData(Person(first_name,last_name),Contact(1111111,Address(123 Main St,New York)),Map(xx -> "yy", zz -> {"aa":"aa","bb":"bb","cc":{}})))
(Assuming the json is not exactly the json above, but a valid similar one)
Code run in Scastie

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.

Deserializing JSON with JSON4S

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)