Given the following case class:
case class ValueItem(key: String, value: String)
and the following json formatter:
implicit val valueItemFormat: Format[ValueItem] = (
(__ \ "key").format[String] and
(__ \ "value").format[String])(ValueItem.apply, unlift(ValueItem.unapply))
a json representation of a ValueItem instance like
ValueItem("fieldname", "fieldValue")
is
{ "key" : "fieldName" , "value" : "fieldValue" }
I'm wondering how to get a json in a flat key/value serialization like
{ "fieldName" : "fieldValue" }
I can't think of a nice way to do this using combinators, since most approaches there require a more direct way to map values at paths to case class fields.
Here's a solution that'll work for objects like {"fieldName" : "fieldValue"}.
import play.api.libs.json._
import play.api.data.validation.ValidationError
implicit val fmt: Format[ValueItem] = new Format[ValueItem] {
def reads(js: JsValue): JsResult[ValueItem] = {
js.validate[JsObject].collect(ValidationError("Not a key-value pair")) {
case JsObject(Seq((key, str: JsString))) => ValueItem(key, str.value)
}
}
def writes(v: ValueItem): JsValue = Json.obj(v.key -> v.value)
}
I've resorted to defining reads and writes manually, as you can see. The Reads is the tricky part, as we're not used to pulling path names into case classes. We can validate the object as a JsObject first, then collect only objects that match the exact structure we're looking for (only one key-value pair, where the value is a JsString). The Writes is much more straightforward, as Json.obj can do exactly what we want.
In action:
scala> Json.parse(""" { "fieldName" : "fieldValue" } """).validate[ValueItem]
res0: play.api.libs.json.JsResult[ValueItem] = JsSuccess(ValueItem(fieldName,fieldValue),)
scala> val item = ValueItem("myKey", "myValue")
item: ValueItem = ValueItem(myKey,myValue)
scala> Json.toJson(item)
res2: play.api.libs.json.JsValue = {"myKey":"myValue"}
Play released it's module for dealing with JSON independent from Play Framework, Play WS
Made a blog post about reading JSON to case classes, but write is pretty similar. check it out at http://pedrorijo.com/blog/scala-json/
Using case classes, and Play WS (already included in Play Framework) you case convert between json and case classes with a simple one-liner implicit
case class User(username: String, friends: Int, enemies: Int, isAlive: Boolean)
object User {
implicit val userJsonFormat = Json.format[User]
}
Related
I am trying to parse json with null values for some fields using Play library. There is a case class which represents the data:
case class Id(value: Int) extends AnyVal
case class Name(value: String) extends AnyVal
case class Number(value: Int) extends AnyVal
case class Data(id: Option[Id], name: Option[Name], number: Option[Number])
Here is how parsing currently works:
def parse(jsValue: JsValue): Try[Seq[Data]] = Try {
jsValue.as[JsArray].value
.flatMap { record =>
val id = Id((record \ "id").as[Int])
val name = Name((record \ "name").as[String])
val number = Number((record \ "number").as[Int])
Some(Data(Some(id), Some(name), Some(number)))
}
}
Parsing with specific data types doesn't handle null cases, so this implementation returns:
Failure(play.api.libs.json.JsResultException: JsResultException(errors:List((,List(JsonValidationError(List(error.expected.jsstring),WrappedArray()))))))
For the input data like this:
{
"id": 1248,
"default": false,
"name": null,
"number": 2
}
I would like to have something like this: Seq(Data(Some(Id(1248)), None, Some(Number(2))))
I am going to write the data into the database so I do not mind writing some null values for these fields.
How can I handle null values for fields in parsed json?
You can simply let the play-json library generate the Reads for your case classes instead of writing them manually:
import play.api.libs.json._
object Data {
implicit val reads: Reads[Data] = {
// move these into the corresponding companion objects if used elsewhere...
implicit val idReads = Json.reads[Id]
implicit val numberReads = Json.reads[Number]
implicit val nameReads = Json.reads[Name]
Json.reads[Data]
}
}
def parse(jsValue: JsValue): Try[Seq[Data]] = Json.fromJson[Seq[Data]](jsValue).toTry
That way, your code will work even in case you change the arguments of your case classes.
If you still want to code it manually, you can use the readNullable parser:
val name: Option[Name] = Name(record.as((__ \ "name").readNullable[String]))
Note, however, that using Try is somewhat frowned upon in FP and directly using JsResult would be more idiomatic.
If you are not locked to use play-json then let me show how it can be done easily with jsoniter-scala:
import com.github.plokhotnyuk.jsoniter_scala.core._
import com.github.plokhotnyuk.jsoniter_scala.macros._
implicit val codec: JsonValueCodec[Seq[Data]] = JsonCodecMaker.make(CodecMakerConfig)
val json: Array[Byte] = """[
{
"id": 1248,
"default": false,
"name": null,
"number": 2
}
]""".getBytes("UTF-8")
val data: Seq[Data] = readFromArray(json)
println(data)
That will produce the following output:
List(Data(Some(Id(1248)),None,Some(Number(2))))
Here you can see an example how to integrate it with the Play framework.
I have a simple case class that im working with in my app, and at some point im changing it to json and sending it to some api call, like this:
import play.api.libs.json.{Json, OFormat}
object MyClassSer {
implicit val MyClassFormat: OFormat[MyClass] = Json.format[MyClass]
}
import MyClassSer._
case class MyClass(a: String, b: String)
val myClass = MyClass("1", "2")
myApiService.post(Json.toJson(myClass))
I wonder if there is a way to change b in the json to Int if I know it always gonna be an int value (but in MyClass unfortunately it has to be string)?
so instead of {"a": "1", "b": "2"} I will send {"a": "1", "b": 2}
can I do this with JsValue somehow or JsObject? I dont want to have another case class and transform it to the other case class..
Use custom writes rather than the default Json.format.....:
import play.api.libs.json.{Json, _}
object MyClass {
implicit val format: OFormat[MyClass] = Json.format[MyClass]
implicit val customwrites: Writes[MyClass] = new Writes[MyClass] {
def writes(myClass: MyClass): JsValue = Json.obj(
"a" -> myClass.a,
"b" -> myClass.b.toInt
)
}
}
case class MyClass(a: String, b: String)
val myClass = MyClass("1", "2")
// use default writes implicitly
println(Json.toJson(myClass)) // {"a":"1","b":"2"}
// use non-default writes explicitly
println(Json.toJson(myClass)(MyClass.customwrites)) // {"a":"1","b":2}
Though I would be careful about converting types like you want to without some sort of fallback, just in case the value isn't an Int.
I have the following object which I am serializing to json using Circe
case class Person(name: String, data: String)
val x = Person("test", s"""{"a": 10, "b":"foo"}""")
import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
println(x.asJson)
The output of the statement above is
{
"name" : "test",
"data" : "{\"a\":10, \"b\":\"foo\"}"
}
but the output I want is
{
"name": "test",
"data": {
"a": 10,
"b": "foo"
}
}
I get the data for the data field from a json based data store. I want to pass it through (so I don't want to unmarshall it into a scala object, only to demarshall it again into json. that marshall/demarshall is a waste of CPU on my server.
So how do I handle such data?
Well, you can write your own Encoder implementation, e.g.:
import io.circe.{Encoder, Json}
import io.circe.jawn.parse
case class Person(name: String, data: String)
implicit val personEncoder: Encoder[Person] = new Encoder[Person] {
override def apply(person: Person): Json = {
val name = Json.fromString(person.name)
val data = parse(person.data) match {
case Left(_) => Json.obj()
case Right(value) => value
}
Json.obj("name" -> name, "data" -> data)
}
}
As a matter of the fact, you have pretty unusual case - one of the fields is a String, that you need to parse, before you put it as a child Json node. Error failure need to be handled somehow - I used empty object, but that isn't necessarily what you would like to use.
If you want to omit the deserialization step... that is not possible. You are building tree of JSON nodes with defined behavior. String cannot suddenly be treated like Json object.
I'm trying (and failing) to get my head around how spray-json converts json feeds into objects. If I have a simple key -> value json feed then it seems to work ok but the data I want to read comes in a list like this:
[{
"name": "John",
"age": "30"
},
{
"name": "Tom",
"age": "25"
}]
And my code looks like this:
package jsontest
import spray.json._
import DefaultJsonProtocol._
object JsonFun {
case class Person(name: String, age: String)
case class FriendList(items: List[Person])
object FriendsProtocol extends DefaultJsonProtocol {
implicit val personFormat = jsonFormat2(Person)
implicit val friendListFormat = jsonFormat1(FriendList)
}
def main(args: Array[String]): Unit = {
import FriendsProtocol._
val input = scala.io.Source.fromFile("test.json")("UTF-8").mkString.parseJson
val friendList = input.convertTo[FriendList]
println(friendList)
}
}
If I change my test file so it just has a single person not in an array and run val friendList = input.convertTo[Person] then it works and everything parses but as soon as I try and parse an array it fails with the error Object expected in field 'items'
Can anyone point me the direction of what I'm doing wrong?
Well, as is often the way immediately after posting something to StackOverflow after spending hours trying to get something working, I've managed to get this to work.
The correct implementation of FriendsProtocol was:
object FriendsProtocol extends DefaultJsonProtocol {
implicit val personFormat = jsonFormat2(Person)
implicit object friendListJsonFormat extends RootJsonFormat[FriendList] {
def read(value: JsValue) = FriendList(value.convertTo[List[Person]])
def write(f: FriendList) = ???
}
}
Telling Spray how to read / write (just read in my case) the list object is enough to get it working.
Hope that helps someone else!
To make the Friend array easier to use extend the IndexedSeq[Person]trait by implementing the appropriate apply and length methods. This will allow the Standard Scala Collections API methods like map, filter and sortBy directly on the FriendsArray instance itself without having to access the underlying Array[Person] value that it wraps.
case class Person(name: String, age: String)
// this case class allows special sequence trait in FriendArray class
// this will allow you to use .map .filter etc on FriendArray
case class FriendArray(items: Array[Person]) extends IndexedSeq[Person] {
def apply(index: Int) = items(index)
def length = items.length
}
object FriendsProtocol extends DefaultJsonProtocol {
implicit val personFormat = jsonFormat2(Person)
implicit object friendListJsonFormat extends RootJsonFormat[FriendArray] {
def read(value: JsValue) = FriendArray(value.convertTo[Array[Person]])
def write(f: FriendArray) = ???
}
}
import FriendsProtocol._
val input = jsonString.parseJson
val friends = input.convertTo[FriendArray]
friends.map(x => println(x.name))
println(friends.length)
This will then print:
John
Tom
2
Hey im pretty new to Spray and reactive mongo .
Im trying to return a list of result as json but i'm having some issue with converting the result to list of json.
this is my model
import reactivemongo.bson.BSONDocumentReader
import reactivemongo.bson.BSONObjectID
import reactivemongo.bson.Macros
case class Post(id: BSONObjectID, likes: Long, message: String, object_id: String, shares: Long)
object Post {
implicit val reader: BSONDocumentReader[Post] = Macros.reader[Post]
}
the Mongo method
def getAll(): Future[List[Post]] ={
val query = BSONDocument(
"likes" -> BSONDocument(
"$gt" -> 27))
collection.find(query).cursor[Post].collect[List]()
}
and this is the route
val route1 =
path("posts") {
val res: Future[List[Post]]= mongoService.getAll()
onComplete(res) {
case Success(value) => complete(value)
case Failure(ex) => complete(ex.getMessage)
}
}
error
type mismatch; found : List[com.example.model.Post] required: spray.httpx.marshalling.ToResponseMarshallable
thanks,
miki
You'll need to define how a Post will be serialized, which you can do via a spray-json Protocol (see the docs for more detailed information). It's quite easy to do so, but before that, you'll also need to define a format for the BSONObjectId type, since there's no built-in support for that type in spray-json (alternatively, if object_id is a string representation of the BSONObjectId, think about removing the id property from your Post class or change it to be a String):
// necessary imports
import spray.json._
import spray.httpx.SprayJsonSupport._
implicit object BSONObjectIdProtocol extends RootJsonFormat[BSONObjectID] {
override def write(obj: BSONObjectID): JsValue = JsString(obj.stringify)
override def read(json: JsValue): BSONObjectID = json match {
case JsString(id) => BSONObjectID.parse(id) match {
case Success(validId) => validId
case _ => deserializationError("Invalid BSON Object Id")
}
case _ => deserializationError("BSON Object Id expected")
}
}
Now, we're able to define the actual protocol for the Post class:
object PostJsonProtocol extends DefaultJsonProtocol {
implicit val format = jsonFormat5(Post.apply)
}
Furthermore, we'll also need to make sure that we have the defined format in scope:
import PostJsonProtocol._
Now, everything will compile as expected.
One more thing: have a look at the docs about the DSL structure of spray. Your mongoService.getAll() isn't within a complete block, which might not reflect your intentions. This ain't an issue yet, but probably will be if your route get more complex. To fix this issue simply put the future into the onComplete call or make it lazy:
val route1 =
path("posts") {
onComplete(mongoService.getAll()) {
case Success(value) => complete(value)
case Failure(ex) => complete(ex.getMessage)
}
}