Scala: How serialize data in JSON? - json

I recently started to learn Scala. I need an EASY way to SERIALIZE my data in Json. Below I will give an example of data. I tried several popular libraries: GSON, Play JSON, Circe, but did not get the result! GSON does not know how to work with Option (eg Option [String]), in Play JSON and Circe they need to describe serialization and deserialization of an abstract class (I don’t know how to get around this).
// Test data
abstract class Base(id: Long, descr: Option[String])
case class A(id: Long, descr: Option[String], a: Option[Long]) extends Base(id, descr)
case class B(id: Long, descr: Option[String], b: Option[Double]) extends Base(id, descr)
case class Data(id: Long, user: Option[String], data: Option[Array[Base]])
val test: Array[Data] = Array(
Data(1, Some("Qqq"), None),
Data(2, None, None),
Data(3, Some("Zzz"), Some(
Array(
A(1, Some("a1"), Some(111)),
A(2, Some("a2"), None),
A(3, None, Some(333)),
B(4, Some("b1"), Some(444.444)),
B(5, Some("b2"), None),
B(6, None, Some(666.666))
)
)) )

Using circe:
import cats.syntax.functor._
import io.circe.{Decoder, Encoder}, io.circe.generic.auto._
import io.circe.syntax._
implicit val encodeBase: Encoder[Base] = Encoder.instance {
case foo # A(_, _, _) => foo.asJson
case bar # B(_, _, _) => bar.asJson
}
And then you can just do
test.asJson

You need to create a companion object for your case class as:
object Data {
import play.api.libs.json._
implicit val read = Json.reads[Data]
implicit val write = Json.writes[Data]
implicit val dataFormat = Json.format[Data]
def tupled = (Data.apply _).tupled
}
Both case class and object must be placed in same file.
Then, when you need to use it, import play.api.libs.json._:
val myObject = new Data(100, None, None)
val myJson = Json.toJson(myObject)
Obs: this is a Play framework library, if you not use this fw, I'm not sure that you can use it.
Another obs ... when you convert an object into a json, all optional parameters, if are None, will not appear into your json value. So, for my example, result will not contain user and data parameters:
{
"id": 100
}

Related

Configure spray-json for non strict parsing deserialization

How to configure the spray-json parsing on parsing options?
Similarly as Jackson Parsing Features.
For example, I am parsing a json that has a field that my case class has not, and it is breaking:
spray.json.DeserializationException: Object is missing required member 'myfield'
UPDATE :
A simple example:
case class MyClass(a: String, b: Long);
and try to parse an incomplete json like
val data = "{a: \"hi\"}"
with a spray-json format like:
jsonFormat2(MyClass.apply)
// ...
data.parseJson.convertTo[MyClass]
(simplified code).
But the question goes further, I want to ask about configuration options like in other parsers. More examples:
Be able to ignore fields that exist in the JSON but not in the case class.
Ways of managing nulls or nonexistent values.
etc.
SprayJson allows you to define custom parsers like so:
case class Foo(a: String, b: Int)
implicit object FooJsonFormat extends RootJsonFormat[Foo] {
override def read(json: JsValue): Foo = {
json.asJsObject.getFields("name", "id") match {
case Seq(JsString(name), id) =>
Foo(name, id.convertTo[Int])
}
}
override def write(obj: Foo): JsValue = obj.toJson
}
This allows you to parse any arbitrary payload and pull out the fields "name" and "id" - other fields are ignored. If those fields are not guaranteed you can add something like:
case Seq(JsString(name), JsNull) =>
Foo(name, 0)
You should look at what's available in JsValue.scala - in particular JsArray may come in handy if you're getting payloads with anonymous arrays (i.e. the root is [{...}] instead of {"field":"value"...})
Spray Json doesn't support default parameters. So You cannot have a case class like
case class MyClass(a: String, b: Int = 0)
and then parse json like {"a":"foo"}
However if you make the second parameter as Option. then it works.
import spray.json._
case class MyClass(a: String, b: Option[Int] = None)
object MyProtocol extends DefaultJsonProtocol {
implicit val f = jsonFormat2(MyClass)
}
import MyProtocol.f
val mc1 = MyClass("foo", Some(10))
val strJson = mc1.toJson.toString
val strJson2 = """{"a": "foo"}"""
val mc2 = strJson2.parseJson.convertTo[MyClass]
println(mc2)

Play 2.3 with ReactiveMongo to serialize JsObject

I have a case class like this:
case class MemberOptions(
#Key("_id") memberId: Long,
settingsJson: JsObject,
updateDate: DateTime = DateTime.now()
)
How can I serialize and deserialize JsObject? I tried to look for Writes and Reads but found nothing yet. There is one plugin reactivemongo-play-json but it is for 2.4 only.
I decided to convert to String if there is no better solution.
import reactivemongo.bson._
implicit object JsObjectHandler extends BSONHandler[BSONString, JsObject] {
override def read(bson: BSONString): JsObject = Json.parse(bson.value).as[JsObject]
override def write(jsObject: JsObject): BSONString = BSONString(jsObject.toString)
}
implicit val handler = Macros.handler[MemberOptions]

How can I define a dynamic base json object?

I would like to design a base trait/class in Scala that can produce the following json:
trait GenericResource {
val singularName: String
val pluralName: String
}
I would inherit this trait in a case class:
case class Product(name: String) extends GenericResource {
override val singularName = "product"
override val pluralName = "products"
}
val car = Product("car")
val jsonString = serialize(car)
the output should look like: {"product":{"name":"car"}}
A Seq[Product] should produce {"products":[{"name":"car"},{"name":"truck"}]} etc...
I'm struggling with the proper abstractions to accomplish this. I am open to solutions using any JSON library (available in Scala).
Here's about the simplest way I can think of to do the singular part generically with circe:
import io.circe.{ Decoder, Encoder, Json }
import io.circe.generic.encoding.DerivedObjectEncoder
trait GenericResource {
val singularName: String
val pluralName: String
}
object GenericResource {
implicit def encodeResource[A <: GenericResource](implicit
derived: DerivedObjectEncoder[A]
): Encoder[A] = Encoder.instance { a =>
Json.obj(a.singularName -> derived(a))
}
}
And then if you have some case class extending GenericResource like this:
case class Product(name: String) extends GenericResource {
val singularName = "product"
val pluralName = "products"
}
You can do this (assuming all the members of the case class are encodeable):
scala> import io.circe.syntax._
import io.circe.syntax._
scala> Product("car").asJson.noSpaces
res0: String = {"product":{"name":"car"}}
No boilerplate, no extra imports, etc.
The Seq case is a little trickier, since circe automatically provides a Seq[A] encoder for any A that has an Encoder, but it doesn't do what you want—it just encodes the items and sticks them in a JSON array. You can write something like this:
implicit def encodeResources[A <: GenericResource](implicit
derived: DerivedObjectEncoder[A]
): Encoder[Seq[A]] = Encoder.instance {
case values # (head +: _) =>
Json.obj(head.pluralName -> Encoder.encodeList(derived)(values.toList))
case Nil => Json.obj()
}
And use it like this:
scala> Seq(Product("car"), Product("truck")).asJson.noSpaces
res1: String = {"products":[{"name":"car"},{"name":"truck"}]}
But you can't just stick it in the companion object and expect everything to work—you have to put it somewhere and import it when you need it (otherwise it has the same priority as the default Seq[A] instances).
Another issue with this encodeResources implementation is that it just returns an empty object if the Seq is empty:
scala> Seq.empty[Product].asJson.noSpaces
res2: String = {}
This is because the plural name is attached to the resource at the instance level, and if you don't have an instance there's no way to get it (short of reflection). You could of course conjure up a fake instance by passing nulls to the constructor or whatever, but that seems out of the scope of this question.
This issue (the resource names being attached to instances) is also going to be trouble if you need to decode this JSON you've encoded. If that is the case, I'd suggest considering a slightly different approach where you have something like a GenericResourceCompanion trait that you mix into the companion object for the specific resource type, and to indicate the names there. If that's not an option, you're probably stuck with reflection or fake instances, or both (but again, probably not in scope for this question).

Generic derivation of AnyVal types with Circe

I want to derive Encoder instances for value classes. With the semiauto mechanism I am not able to derive nested classes.
Image the following case class structure
{
case class Recipient(email: Recipient.Email, name: Recipient.Name)
object Recipient {
case class Email(value: String) extends AnyVal
case class Name(value: String) extends AnyVal
}
}
In an ammonite shell ( also add the Recipient case classes )
load.ivy("io.circe" %% "circe-core" % "0.6.1")
load.ivy("io.circe" %% "circe-generic" % "0.6.1")
import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._
Now deriving a decoder for Email results as expected in
Recipient.Email("ab#cd.com").asJson(deriveEncoder[Recipient.Email])
Json = {
"value" : "ab#cd.com"
}
Deriving an Encoder[Recipient] doesn't work
deriveDecoder[Recipient]
could not find Lazy implicit value of type
io.circe.generic.decoding.DerivedDecoder[$sess.cmd5.Recipient]
What I would like to do is deriving an Encoder[Recipient.Email] that returns the wrapped type. This little piece works if I derive the codec explicitly.
import shapeless.Unwrapped
implicit def encodeAnyVal[W <: AnyVal, U](
implicit unwrapped: Unwrapped.Aux[W, U],
encoderUnwrapped: Encoder[U]): Encoder[W] = {
Encoder.instance[W](v => encoderUnwrapped(unwrapped.unwrap(v)))
}
Recipient.Email("ab#cd.com").asJson(encodeAnyVal[Recipient.Email, String])
res11: Json = "ab#cd.com"
Still I can't derive a Encoder[Recipient]
implicit val emailEncoder: Encoder[Recipient.Email] = encodeAnyVal[Recipient.Email, String]
implicit val nameEncoder: Encoder[Recipient.Name] = encodeAnyVal[Recipient.Name, String]
deriveDecoder[Recipient]
cmd14.sc:1: could not find Lazy implicit value of type io.circe.generic.decoding.DerivedDecoder[$sess.cmd5.Recipient]
Has anyone done something similar?
Thanks in advance,
Muki
You should add implicit witness instance instead of type bound. Going to end up with something like this:
implicit def encodeAnyVal[W, U](
implicit ev: W <:< Anyval,
unwrapped: Unwrapped.Aux[W, U],
encoderUnwrapped: Encoder[U]): Encoder[W] = {
Encoder.instance[W](v => encoderUnwrapped(unwrapped.unwrap(v)))
}

spray-json recursive json issue - could not find implicit value for evidence parameter

Hello I am bit struggling with parsing a json with spray-json libary whith recursive data structure.
I have following case class structure and Protocol defined
import spray.json.DefaultJsonProtocol
import spray.json._
case class OfferAnalysisReport(BUG: DleTN, DOM: DleTN) extends AnalyticalReport
case class DleTN(doc_count: Int, result: AggsDefinition)
case class BucketDefinition(key: String, doc_count: Int, result: Option[AggsDefinition])
case class AggsDefinition(buckets: List[BucketDefinition])
object OfferAnalysisReportProtocol extends DefaultJsonProtocol {
implicit val aggsDefFormat = lazyFormat(jsonFormat(AggsDefinition,"buckets"))
implicit val bucketDefinitionFormat = lazyFormat(jsonFormat(BucketDefinition,"key","doc_count","result"))
implicit val dleTypNemovitostisFormat = jsonFormat2(DleTypuNemovitosti)
implicit val OfferAnalysisReportFormat = jsonFormat2(OfferAnalysisReport)
}
In the test I am importing:
import spray.json._
import OfferAnalysisReportProtocol._
But I am still getting
Error: couldn't find implicit value for evidence parameter of type BuckedDefinition.
Am I missing something important here? Could someone give me a hint?
You will have to wrap your format constructor with lazyFormat as you have done, but you also have to supply an explicit type annotation like this:
implicit val fooFormat: JsonFormat[Foo] = lazyFormat(jsonFormat(Foo, "i", "foo"))
Source: https://github.com/spray/spray-json#jsonformats-for-recursive-types
#spydons solution did not work for me. You can handle recursive classes by writing a custom serializer as well. Here's an example for writing:
object Test extends App{
import spray.json._
case class Foo(i: Int, foo: Foo)
implicit object FooJsonFormat extends RootJsonWriter[Foo] {
override def write(c: Foo) : JsValue =
if (c.foo != null) { JsObject("i" -> JsNumber(c.i),"foo" -> write(c.foo)) }
else { JsObject("i" -> JsNumber(c.i)) }
}
println(Foo(20,Foo(10,null)).toJson) //prints {"foo":{"i":10},"i":20}
}