I'm new at scala. I have a Json File that I rea it into String. Then I'm parsing the string to a JSValue. Now I'm trying to read all values to update my database, but i don't know how to proceed.
val l = scala.io.Source.fromFile("list.json").getLines().mkString
val result: JsValue = Json.parse(l)
My Json is like :
{
picture_id : xxx
width : xxx
height : xxx
},
{
picture_id : xxx
width : xxx
height : xxx
},
....
I want to extract even block to update database with right values.
Thx.
You should do something like below. I hope the comments are explanatory:
val l = scala.io.Source.fromFile("list.json").getLines().mkString
val result: JsValue = Json.parse(l)
//Create a model to hold your json objects
case class Pic(id: String, width: String, height: String)
//Create a reader that reads your json string to your model(Pic)
implicit val picReads: Reads[Pic] = (
(JsPath \ "picture_id").read[String] and
(JsPath \ "width").read[String] and
(JsPath \ "height").read[String] and
)(Pic.apply _)
result.validate[List[Pic]] match {
case s: JsSuccess[List[Pic]] =>
//Deal with your list of pics here
case e: JsError => println("Errors: " + JsError.toFlatJson(e).toString())
}
Related
My case class has 30 fields. For simplicity, I use 4 fields,
case class Person(id: Long, name: String, age: Int, sex: Sex)
val personFormat1: OFormat[(Long, String)] = ((__ \ "id").format[Long] ~ (__ \ "name").format[String]).tupled
val personFormat2: OFormat[(Int, Sex)] = ((__ \ "age").format[Int] ~ (__ \ "sex").format[Sex]).tupled
implicit val personFormat: Format[Person] = (personFormat1 ~ personFormat2)({
case ((id, name), (age, sex)) => new Person(id, name, age, sex)
}, (person: Format) => ((person.id, person.name), (person.age, person.sex)))
But even after writing formatter with format1 as a group of 22 fields and format2 as a group of 8 fields, I get error when am trying to parse the json of this case class.
Error is
No Json serializer as JsObject found for type Person. Try to implement an implicit OWrites or OFormat for this type.
How to write implicit Owrites or OFormat? or how to fix this issue?
I use Play-Json extensions library for working with JSON with more than 22 fields: https://github.com/xdotai/play-json-extensions
libraryDependencies += "ai.x" %% "play-json-extensions" % "0.8.0"
You need to an implicit writer to do this. Something like this
implicit val person= Json.format[Person]
Also, if you are using custom data types, like for your case Sex you need to specify a reader and writer. You do not need to do this for primitive types like Int, Long, String Etc.
def enumReads[T <: Enum[T]](mkEnum: String => T) = new Reads[T] {
override def reads(json: JsValue): JsResult[T] =
json match {
case JsString(s) => try {
JsSuccess(mkEnum(s))
}
catch {
case e: IllegalArgumentException =>
JsError("Not a valid enum value: " + s)
}
case v => JsError("Can't convert to enum: " + v)
}
}
implicit val enumWrites = new Writes[Enum[_]] {
def writes(e: Enum[_]) = JsString(e.toString)
}
implicit val sex = enumReads(Sex.valueOf)
Also, upgrade to scala 2.11 or later to avoid the limitation of 22 fields in case class. For more info see here: How to get around the Scala case class limit of 22 fields?
I found this using google, and this worked greatly for me
// https://mvnrepository.com/artifact/com.chuusai/shapeless_2.11
libraryDependencies += "com.chuusai" % "shapeless_2.11" % "2.3.2"
// https://mvnrepository.com/artifact/org.julienrf/play-json-derived-codecs_2.11
libraryDependencies += "org.julienrf" % "play-json-derived-codecs_2.11" % "3.2"
I'm trying to create Json readers in my Play framework application (Scala). The problem is, part of my Json is a little funky, and requires further processing to retrieve the values. For example:
{
"field1":"value1",
"field2":"value/1",
"num2":2
}
with case classes:
case class Field1(text: String, fields: Field2)
case class Field2(text: String, num: Int, num2: Int)
Basically the text and num fields for Field2 are derived from the value value/1, by splitting the text. Here's the splitter function:
def splitter(path: String, num2: Int): Field2 = {
val split = path.split("\\")
Field2(split(0), split(1).toInt, num2)
}
This is fairly straightforward, the actual splitter function is far more complex. Basically the only way to construct this object Field2 is to pass a single string to a function that spits out the required object.
How do I go about creating a reader for Field2 (and by extension for Field1)?
Here's what I have so far:
object Field1 {
implicit val reader = (
(__ \ "field1").read[String] and
(__).read[Field2]
) (Field1.apply _)
}
object Field2 {
implicit val reader = (
splitter((__ \ "field2").read[String], (__ \ "num2"))
) // Obviously incorrect syntax + type mismatch, but this is roughly what I'm trying to accomplish.
}
If you use the non-functional combinator syntax it becomes a little simpler I think:
object Field2 {
implicit val reader = Reads[Field2] { json =>
for {
path <- (json \ "field2").validate[String]
num2 <- (json \ "num2").validate[Int]
} yield splitter(path, num2)
}
}
Additionally if you want splitter to further validate the input have it return a JsResult[Field2] like this:
def splitter(path: String, num2: Int): JsResult[Field2] = {
val split = path.split("\\")
if (split.size != 2) {
JsError(s"$path must be of the form: {field}\\{num}")
} else {
Try(Field2(split(0), split(1).toInt, num2)).map(JsSuccess(_)).getOrElse {
JsError(s"${split(1)} is not a valid Int")
}
}
}
And then modify the reader:
object Field2 {
implicit val reader = Reads[Field2] { json =>
for {
path <- (json \ "field2").validate[String]
num2 <- (json \ "num2").validate[Int]
field2 <- splitter(path, num2)
} yield field2
}
}
If you still prefer using the functional syntax and you don't mind the lack of validation that splitter does try this:
def splitter(path: String, num2: Int): Field2 = {
val split = path.split("\\")
Field2(split(0), split(1).toInt, num2)
}
implicit val reader = (
(__ \ "field2").read[String] and
(__ \ "num2").read[Int]
)(splitter _)
I'd recommend against this, it's unsafe (split(1) and toInt both might throw exceptions) and the functional syntax can have performance issues. See https://www.lucidchart.com/techblog/2016/08/29/speeding-up-restful-services-in-play-framework/.
I don't know why do you need case classes, but you can also transform json for your needs with
(__ \ "field2" \ "num2").json.copyFrom((__ \ "num2").json.pick) and
(__ \ "field2").json.update(
of[String].map { o =>
val split = o.split("/")
Json.obj(
"text" -> split(0),
"num" -> split(1).toInt
)
}
)
).reduce andThen (__ \ "num2").json.prune
scala> j: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"field2":{"num2":2,"text":"value","num":1},"field1":"value1"},/num2)
and then you can use your Reads[Field1]
I represent a weeks data with a case class that has a list of days datas with other relevant parameters affecting the weeks datas:
case class WeeksData (
data: List[DaysData],
otherStuffRelatedToWeeksParameters: ...
)
case class DaysData (
stuff: ...
)
I need to validate the input Json from the client so that the WeeksData data parameter has exactly 7 elements. How would you suggest I do that?
So far I have
val weeksDataValidator: Reads[List[DaysData]] = ???
implicit val weeksDataWrites = Json.writes[WeeksData]
implicit val weeksDataReads: Reads[WeeksData] = (
(JsPath \ "weeksData").read[List[DaysData]](weeksDataValidator))(WeeksData.apply _)
I know I can write for Int:
val intValidator: Reads[Int] = min(0) keepAnd max(999)
So, how to write a validator for an array/list so that the required length is fixed (7 in this case)?
Or should I just modify the WeeksData class to such as
case class WeeksData (
mon: DaysData,
tue: DaysData,
wed: DaysData,
...
otherStuffRelatedToWeeksParameters: ...
)
Thanks
Model is simplified for the sake of short example:
case class A(weeks: List[Int])
def listReads[T](length: Int)(implicit anyListReads: Reads[List[T]]) = Reads[List[T]] { js =>
anyListReads.reads(js).filter(JsError(ValidationError(s"Length of the list must be $length")))(_.size == length)
}
implicit val reads: Reads[A] = ((JsPath \ "weeks").read[List[Int]](listReads[Int](2))).map(A.apply _)
Usage (from REPL):
scala> Json.parse("""{ "weeks": [1, 2] }""").validate[A]
res1: play.api.libs.json.JsResult[A] = JsSuccess(A(List(1, 2)),/weeks)
scala> Json.parse("""{ "weeks": [1] }""").validate[A]
res2: play.api.libs.json.JsResult[A] = JsError(List((/weeks,List(ValidationError(List(error.minLength),WrappedArray(2))))))
{
"location":{
"residents":[{
"renting":[{
"name":"John Doe"
"pets":"2"
},{
"name":"Jane Smith"
"pets":"2"
}]
}]
}
}
I can successfully traverse location with this -
val json = ...
val rentReads = (__ \ 'location).read[String]
val rentResult = json.validate[String](rentReads)
rentResult match {
case s: JsSuccess[String] => Ok(s.get)
case e: JsError => Ok("Errors: " + JsError.toFlatJson(e).toString())
}
Based on the documentation, I should be able to do something like this -
val skillReads = ((__ \ 'location) \ 'residents)(0).read[String]
but it results in the following error -
Errors: {"obj.VariationResultsWrapper.VariationResultSets[0]":[{"msg":"error.path.missing","args":[]}]}
At this point I'm just trying to understand how to return values from "renting" only. Eventually, I would like to map that result to a case class.
If your eventual goal is to parse this into case classes, just define those case classes and let Play do the heavy lifting.
case class Renting(name: String, pets: String)
case class Resident(renting: List[Renting])
case class Location(residents: List[Resident])
implicit val rentingFormat = Json.format[Renting]
implicit val residentFormat = Json.format[Resident]
implicit val locationFormat = Json.format[Location]
(json \ "location").validate[Location]
res1: play.api.libs.json.JsResult[Location] = JsSuccess(Location(List(Resident(List(Renting(John Doe,2), Renting(Jane Smith,2))))),/residents)
I use play framework version 2.2.1 with scala.
I have a large json-string that i wish to construct an object from.
I have written the formatters for the classes.
My question is what are the steps i am supposed to cast that string to concrete class.
e.g of code that i've tried:
val something = scala.util.parsing.json.JSON.parseFull(jsonContent).asInstanceOf[HsmTenant] //This has stucked my debug session and operation never completes
Or
val concreteClass = Json.parse(jsonContent).asInstanceOf[T] //message: "Error processing request - Failed to migrate, Exception=play.api.libs.json.JsObject cannot be cast to migration.hsm.HsmTenant"
When trying:
Json.parse(jsonContent).as[T]
I get the following:
Multiple markers at this line
- No Json deserializer found for type migration.hsm.HsmTenant. Try to implement an implicit Reads or Format for this type.
- not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[migration.hsm.HsmTenant])migration.hsm.HsmTenant. Unspecified value
parameter fjs.
- No Json deserializer found for type migration.hsm.HsmTenant. Try to implement an implicit Reads or Format for this type.
- not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[migration.hsm.HsmTenant])migration.hsm.HsmTenant. Unspecified value
parameter fjs.
- Line breakpoint:HsmParser [line: 16] - parse(jsonContent: String): migration.hsm.HsmTenant
My read and writes looks like: and they compile:
trait HsmFormats {
implicit val hsmRetailerFormat = Json.format[Retailer]
implicit val hsmproductFormat = new Format[HsmProduct]
{
def writes(item: HsmProduct): JsValue =
{
val retailers = item.r.getOrElse(List[Retailer]())
val images = item.images.getOrElse(List[String]())
Json.obj(
"u" -> item.u,
"s" -> item.s,
"z" -> item.z,
"n" -> item.n,
"v" -> item.v,
"vu" -> item.vu,
"t" -> item.t,
"r" -> retailers,
"images" -> images
)
}
def reads(json: JsValue): JsResult[HsmProduct] =
{
val retailers = (json \ "r").as[Option[List[Retailer]]]
var retaliersReal:Option[List[Retailer]] = None
if (retailers.isDefined)
{
retaliersReal = Some(retailers.get)
}
val images = (json \ "images").as[Option[List[String]]]
var imagesReal:Option[List[String]] = None
if (images.isDefined)
{
imagesReal = Some(images.get)
}
JsSuccess(new HsmProduct(
(json \ "u").as[String],
(json \ "s").as[Int],
(json \ "z").as[Int],
(json \ "n").as[String],
(json \ "v").as[String],
(json \ "vu").as[String],
(json \ "t").as[String],
retailers,
imagesReal
))
}
}
implicit val hsmTenantFormat = new Format[HsmTenant]
{
def writes(tenant: HsmTenant): JsValue =
{
val items = tenant.items.getOrElse(List[HsmProduct]())
Json.obj(
"items" -> tenant.items,
"prefixAndroid" -> tenant.prefixAndroid,
"prefixIOS" -> tenant.prefixIOS,
"er" -> tenant.er,
"erMessage" -> tenant.erMessage
)
}
def reads(json: JsValue): JsResult[HsmTenant] =
{
val items = (json \ "items").as[Option[List[HsmProduct]]]
var itemsReal:Option[List[HsmProduct]] = None
if (items.isDefined)
{
itemsReal = Some(items.get)
}
JsSuccess(new HsmTenant(
itemsReal,
(json \ "prefixAndroid").as[String],
(json \ "prefixIOS").as[String],
(json \ "er").as[Int],
(json \ "erMessage").as[String]
))
}
}
This should work if your Formats are correct:
import play.api.libs.json.Json
Json.parse(jsonContent).as[T]
What was the problem ?
It was not issue if import the Formats class, my format is a trait, so i must extend that trait in classes i use it.