json to scala case class - scala

I am trying to read the below json into scala case class. I am able to bind the case class to json using json4s.
The problem is the expectedTypes would change for every table. It could be more or less number of elements and the name would be different. How to create a case class for this requirement?
{
"filepattern": "product*.gzip",
"replaceheader": "productid,name,market",
"dataType": [
{
"expectedTypes": {
"productId": "DOUBLE",
"name": "STRING"
}
}
]
}
case class ExpectedTypes(
productid: String,
name: String
)
case class DataType(
expectedTypes: ExpectedTypes
)
case class table(
filepattern: String,
replaceheader: Option[String],
dataType: List[DataType]
)

If it's not predictable how many fields are you going to have in expectedTypes, you can use Map:
case class Root(
filepattern: String,
replaceheader: Option[String],
dataType: List[DataType]
)
case class DataType(
expectedTypes: Map[String, String]
)

Related

Building Reads converter and case class at runtime in Play Framework

I have a file that contains the following array of JSON objects:
[
{
"type": "home",
"number": 1111
},
{
"type": "office",
"number": 2222
},
{
"type": "mobile",
"number": 3333
}
]
In Play Framework 2.x I would define an implicit Reads converter to read the file and convert it to a Scala structure:
implicit val implicitRead : Reads[MyClass] = (
(JsPath \ "type").read[String] and
(JsPath \ "number").read[Int]
) (MyClass.apply _)
the Scala case class defined as:
case class MyClass (myType: String, myNumber: Int)
and parsing the JSON with:
val json = // file record content
json.validate[MyClass] match {
case s: JsSuccess[MyClass] => {
val myObject: MyClass = s.get
// do something with myObject
}
case e: JsError => {
// error handling flow
}
Now, my problem is that I know the structure of the JSON file only at runtime, not at compilation time. Is it possible to build both the implicit Reads converter and the case class at runtime?
Use case classes directly with play-json:
Change the case class to:
case class MyClass (`type`: String, number: Int)
Add the json-formatter to the companion object:
object MyClass {
implicit val format = Json.format[MyClass]
}
The validate function looks now:
val myClass = // file record content
json.validate[Seq[MyClass]] match {
case JsSuccess(myClasses, _) => myClasses
case e: JsError => // handle error case
}
That's all you need. If you are not happy with the parameter names, you can use a Wrapper case class.

Macros or Scala Reflect - extract all parameter names as a list of string routes

Given I have a case class with a lot of different parameter types, I want to collect all parameter names in a dot notation.
Example:
case class Language(name: String, locale: String)
case class Author(name: String)
case class ContentType(name: String)
case class Content(text: String, contentType: ContentType, created: DateTime)
case class Page(language: Language, author: Author, content: Content, description: Option[String])
This would return:
Seq(
"language.name",
"language.locale"
"author.name",
"content.text",
"content.contentType.name",
"content.created",
"description",
"description.value"
)
The main issue here is that the macros expansion I'm using is very very expensive, so I feel the macro solution is no the best route to solve this problem or the implementation is very poor.
Is there a better way for 2.11.8+?, 2.11.6works fine
Seems to be a use case for Scalameta
import scala.meta._
import scala.meta.dialects.Scala212
println(
"""
|case class Language(name: String, locale: String)
|case class Author(name: String)
|case class ContentType(name: String)
|case class Content(text: String, contentType: ContentType, created: DateTime)
|case class Page(language: Language, author: Author, content: Content, description: Option[String])
""".stripMargin.parse[Source].get.collect {
case q"..$_ class $tname[..$_] ..$_ (...$paramss) extends $_" =>
paramss.flatten.map { case param"..$_ $pname: $_ = $_" =>
s"$tname.$pname"
}
}.flatten
)
//List(Language.name, Language.locale, Author.name, ContentType.name, Content.text, Content.contentType, Content.created, Page.language, Page.author, Page.content, Page.description)

How to feed JSON to CASE CLASS directly using functional programming in scala?

{
"cars": [{
"amount": 120.00,
"name": "Car1"
}, {
"amount": 245.00,
"name": "Car2"
}]
}
I am reading above JSON as following in my Controller
val body: JsObject = request.body.asInstanceOf[JsObject]
I am having following CASE CLASS
case class BIC(name: String, amount: Double)
I want to create List[BIC] objects by reading data from JSON [e.g. body] using Functional style
Use Play JSON.
Example:
case class Wrapper(cars: List[Bic])
case class BIC(name: String, amount: Double)
Then in your controller:
implicit val wrapperFormats = Json.format[Wrapper]
implicit val bICFormats = Json.format[BIC]
def postCars(): Action[JsValue] = Action(json.parse) { implicit request =>
request.body.validate[Wrapper] match {
case JsSuccess(obj, _) => {
//do something with obj.
}
case JsError(err) => {
BadRequest(
JsObject(
"error" -> err.toString
)
)
}
}
}
Please note that I am returning Action[JsValue] this is so JQuery will run success when using AJAX.
I hope this helps,
Rhys
another reference:
https://www.playframework.com/documentation/2.5.x/ScalaJsonCombinators
First, define two case classes for your model like this :
object Models {
case class Bic(name : String, amount : Double)
object Bic {
implicit val BicFormat = Json.format[Bic]
}
case class Cars(bics : List[Bic])
object Cars {
implicit val CarsFormat = Json.format[Cars]
}
}
You're using the Play Framework so you can use the JSON library.
In your controller, if you want to read the bics, you can do it like that :
def getCars = Action(parse.json) { request =>
request.body.validate[Cars] map { cars =>
// treat your cars ..
}
}

Escape characters in a dynamic List

I'll like to escape characters in a dynamic List used in creating a case class.
case class Profile(biography: String,
userid: String,
creationdate: String) extends Serializable
object Profile {
val cs = this.getClass.getConstructors
def createFromList(params: List[Any]) = params match {
case List(biography: Any,
userid: Any,
creationdate: Any) => Profile(StringEscapeUtils.escapeJava(biography.asInstanceOf[String]),
StringEscapeUtils.escapeJava(creationdate.asInstanceOf[String]),
StringEscapeUtils.escapeJava(userid.asInstanceOf[String]))
}
}
JSON.parseFull("""{"biography":"An avid learner","userid":"165774c2-a0e7-4a24-8f79-0f52bf3e2cda", "creationdate":"2015-07-13T07:48:47.924Z"}""")
.map(_.get.asInstanceOf[scala.collection.immutable.Map[String, Any]])
.map {
m => Profile.createFromList(m.values.to[collection.immutable.List])
} saveToCassandra("testks", "profiles", SomeColumns("biography", "userid", "creationdate"))
I get this error:
scala.MatchError: List(An avid learner, 165774c2-a0e7-4a24-8f79-0f52bf3e2cda, 2015-07-13T07:48:47.925Z) (of class scala.collection.immutable.$colon$colon)
Any ideas please?
It might be simpler to use a different (external) JSON library than scala.util.parsing.json (which has been deprecated since Scala 2.11).
There are a lot of good Scala Json libraries, below an example using json4s.
import org.json4s._
import org.json4s.native.JsonMethods._
case class Profile(biography: String, userid: String, creationdate: String)
val json = """{
| "biography":"An avid learner",
| "userid":"165774c2-a0e7-4a24-8f79-0f52bf3e2cda",
| "creationdate":"2015-07-13T07:48:47.924Z"
|}""".stripMargin
parse(json).extract[Profile]
// Profile(An avid learner,165774c2-a0e7-4a24-8f79-0f52bf3e2cda,2015-07-13T07:48:47.924Z)

Flatten extranous fields when extracting using LiftJson or Json4s

I want to extract using LiftJson or Json4s the following Json (not quite but something similar) to the following case classes.
{
"data": [
{
"id": "1234",
"message": "Test",
"comments": {
"data": [
{
"id": "4321",
"content": "Test2",
}
]
}
}
The case classes:
case class A(id: String, message: string, comments: List[B])
case class B(id: String, content: String)
For the top level I can do: (val \ "data").extract[List[A]] to flatten the extra data field. But for the second level one, I don't see I way to use extract DIRECTLY.
Could I use a custom serializer (exemple here) or any of the following function (json4s) to remove the extraneous "data" field? Or any idea to make it simple?
def mapField(f: JField => JField): JValue
def transformField(f: PartialFunction[JField, JField]): JValue
Want I want to avoid is creating others intermidiate case class to extract the data, and then create the shown case class with it.
I found the solution a while ago, but haven't had time to reply. I was thinking backward, this is easy:
def transformListData(src: JValue, field: String): JValue = {
src.transformField {
case JField(x, v) if x == field => JField(field, v \ "data")
}
}
transformListData(json, "comments")
The following would remove the extra data and would flatten the list.