Serializing sequences of AnyVal with json4s - json

I have a problem when trying to serialize sequences of AnyVal using json4s in scala.
Here is a test using FunSuite that reproduces the problem:
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.json4s.jackson.Serialization._
import org.scalatest.{FunSuite, Matchers}
case class MyId(id: String) extends AnyVal
case class MyModel(ids: Seq[MyId])
class AnyValTest extends FunSuite with Matchers {
test("should serialize correctly") {
implicit val formats = DefaultFormats
val model = MyModel(Seq(MyId("1"), MyId("2")))
val text = write(model)
parse(text).extract[MyModel] shouldBe model
}
}
The test fails when trying to extract MyModel from the JValue because it can not find a suitable value for the ids field.
I notice that it AnyVal are working fine when used directly though with something like:
case class AnotherModel(id: MyId)
Then I am able to serialise and deserialise correctly.

I know this question is one year old but I ran into the same issue. Writing what I did in case it helps someone else. You will need a custom serializer.
case class Id(asString: String) extends AnyVal
class NotificationSerializer extends CustomSerializer[Id](format ⇒ (
{case JString(s) => Id(s)},
{case Id(s) => JString(s)}))
Without above serialization, your JSON will look something like
{"ids":[[{"asString":"testId1"},{"asString":"testId2"}]]}
I am not entirely sure why AnyVal case class serialization works fine when it is a part of another case class but not standalone. My best guess is that the behavior is due to the allocation behavior of JVM for array containing value classes. See http://docs.scala-lang.org/overviews/core/value-classes.html for 'when allocation is necessary' section.

Related

Json4s Jackson read write case class not equal

How can this be that a previously serialised and then deserialised case class is not equal:
import org.json4s.DefaultFormats
import org.json4s.jackson.Serialization.{write, read}
implicit val formats: DefaultFormats = DefaultFormats
case class MyCaseTestClass(two: String, num: Int)
read[MyCaseTestClass](write(MyCaseTestClass("bla", 123))) shouldBe MyCaseTestClass("bla", 123)
And running this I get:
MyCaseTestClass(bla,123) was not equal to MyCaseTestClass(bla,123)
ScalaTestFailureLocation: ...
Expected :MyCaseTestClass(bla,123)
Actual :MyCaseTestClass(bla,123)
Ok, after trying around a while I found that defining a case class inside your unit test class that is enriched with FlatSpec with Matchers this causes the issue somehow.
If defining the class outside, e.g. in a separate file / object, this doesn't happen.

Circe asJson not encoding properties from abstract base class

Suppose I have the following abstract base class:
package Models
import reactivemongo.bson.BSONObjectID
abstract class RecordObject {
val _id: String = BSONObjectID.generate().stringify
}
Which is extended by the following concrete case class:
package Models
case class PersonRecord(name: String) extends RecordObject
I then try to get a JSON string using some code like the following:
import io.circe.syntax._
import io.circe.generic.auto._
import org.http4s.circe._
// ...
val person = new PersonRecord(name = "Bob")
println(person._id, person.name) // prints some UUID and "Bob"
println(person.asJso) // {"name": "Bob"} -- what happened to "_id"?
As you can see, the property _id: String inherited from RecordObject is missing. I would expect that the built-in Encoder should function just fine for this use case. Do I really need to build my own?
Let's see what happens in encoder generation. Circe uses shapeless to derive its codecs, so its enough to check what shapeless resolves into to answer your question. So in ammonite:
# abstract class RecordObject {
val _id: String = java.util.UUID.randomUUID.toString
}
defined class RecordObject
# case class PersonRecord(name: String) extends RecordObject
defined class PersonRecord
# import $ivy.`com.chuusai::shapeless:2.3.3`, shapeless._
import $ivy.$ , shapeless._
# Generic[PersonRecord]
res3: Generic[PersonRecord]{type Repr = String :: shapeless.HNil} = ammonite.$sess.cmd3$anon$macro$2$1#1123d461
OK, so its String :: HNil. Fair enough - what shapeless does is extracting all fields available in constructor transforming one way, and putting all fields back through constructor if converting the other.
Basically all typeclass derivation works this way, so you should make it possible to pass _id as constructor:
abstract class RecordObject {
val _id: String
}
case class PersonRecord(
name: String,
_id: String = BSONObjectID.generate().stringify
) extends RecordObject
That would help type class derivation do its work. If you cannot change how PersonRecord looks like... then yes you have to write your own codec. Though I doubt it would be easy as you made _id immutable and impossible to set from outside through a constructor, so it would also be hard to implement using any other way.

Serialize extended class to JSON in Scala with Play Framework serializer

I would like to serialize an extended class in scala and
i have some test code..
import org.specs2.mutable._
import org.specs2.runner._
import org.junit.runner._
import play.api.libs.json.Json
#RunWith(classOf[JUnitRunner])
class JsonSerializerTest extends Specification {
class A(val s1: String)
case class B(s2: String) extends A("a")
"Application" should {
"serialize class to JSON" in {
implicit val bWrites = Json.writes[B]
implicit val bReads = Json.reads[B]
val bClass = B("b")
println(bClass.s1 + " " + bClass.s2)
val serialized = Json.toJson[B](bClass)
val s1 = (serialized \ "s1").asOpt[String]
s1 should beSome[String]
}
}
}
In this case test print:
a b
Application should
'None' is not Some
java.lang.Exception: 'None' is not Some
It means that s1 field from parent class were not serialized.
The solution
class A(val s1: String)
case class B(override val s1: String, s2: String) extends A(s1)
mostly unacceptable because in real application classes have a lot of fields and specifying them explicitly every time when i extend class complicates the code.
Is there any other solution for this case?
you can manually create the json serializers (described here: https://www.playframework.com/documentation/2.5.x/ScalaJsonCombinators)
the problem with your version is that Json.writes and Json.reads are macros that are looking specifically at the constructor of the case class, and building the serializer from that (so superclass arguments aren't captured). you could copy and roll your own version of the macro: https://github.com/playframework/playframework/blob/d6c2673d91d85fd37de424951ee5ad9f4f4cce98/framework/src/play-json/src/main/scala/play/api/libs/json/JsMacroImpl.scala
lastly, you can make a function that takes the result of Json.writes and Json.reads, and adds on the shared fields you want. something like:
object A {
def writesSubclass[T](writer: Writes[T]): Writes[T] = new Writes[T] {
def writes(t: T) = Json.obj("s1" -> t.s1) ++ writer.writes(t).as[JsObject]
}
}
implicit val bWrites = A.writesSubclass(Json.writes[B])
depending on how often your A gets extended, this might be your best bet.

Scala activerecord with implicit json format

I have a scala-activerecord:
case class Person(name: String) extends ActiveRecord with Timestamps
object Person extends ActiveRecordCompanion[Person]
Everything works ok.
Suddenly, I want to provide an API and repond with json representation of the entity, so I modified the code:
case class Person(name: String) extends ActiveRecord with Timestamps
object Person extends ActiveRecordCompanion[Person] with DefaultJsonProtocol {
implicit val jsonFormat = jsonFormat1(Request)
}
Now it causes an exception:
com.github.aselab.activerecord.SchemaSettingException: Cannot find table definition of class Person
at com.github.aselab.activerecord.ActiveRecordException$.tableNotFound(ActiveRecordException.scala:48)
at com.github.aselab.activerecord.Config$$anonfun$schema$1.apply(ActiveRecordConfig.scala:29)
at com.github.aselab.activerecord.Config$$anonfun$schema$1.apply(ActiveRecordConfig.scala:29)
at scala.collection.MapLike$class.getOrElse(MapLike.scala:128)
at scala.collection.AbstractMap.getOrElse(Map.scala:59)
at com.github.aselab.activerecord.Config$.schema(ActiveRecordConfig.scala:29)
at com.github.aselab.activerecord.ActiveRecordBaseCompanion$class.schema(ActiveRecord.scala:116)
at Person$.schema$lzycompute(Request.scala:12)
at Person$.schema(Request.scala:12)
at com.github.aselab.activerecord.ActiveRecordBaseCompanion$class.table(ActiveRecord.scala:123)
at Person$.table$lzycompute(Request.scala:12)
at Person$.table(Request.scala:12)
at com.github.aselab.activerecord.ActiveRecordBaseCompanion$class.all(ActiveRecord.scala:133)
at Person$.all(Request.scala:12)
at com.github.aselab.activerecord.inner.CompanionIterable$class.companionToIterable(Implicits.scala:15)
at Person$.companionToIterable(Request.scala:12)
at Person$.<init>(Request.scala:13)
at Person$.<clinit>(Request.scala)
... 34 more
EDIT:
I put two breakpoints in ActiveRecordConfig.scala:
Breakpoint A here:
def schema(companion: ActiveRecordBaseCompanion[_, _]): ActiveRecordTables = {
val clazz = companion.classInfo.clazz
tables.getOrElse(clazz, throw ActiveRecordException.tableNotFound(clazz.toString))
}
Breakpoint B here:
def registerSchema(s: ActiveRecordTables) = {
conf = s.config
s.all.foreach(t => _tables.update(t.posoMetaData.clasz, s))
}
With the first code (without json implicit) the execution hits the breakpoint B.
With the second code (including json implicit) the execution hits the breakpoint A first, causing the exception.
Json support work in the version 0.3.1 of scala-activerecord, see wiki and this issue. As for now, with the latest version 0.3.0 you can use the first code and the form values deserialization:
case class Person(name: String) extends ActiveRecord with Timestamps
object Person extends ActiveRecordCompanion[Person]
And in your e.g. spray controller:
import spray.httpx.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
requestContext.complete(Person.find(id).toFormValues)
The method toFormValues will return Map[String, String], which can be by spray-json implicitly converted to json.

Convert polymorphic case classes to json and back

I am trying to use spray-json in scala to recognize the choice between Ec2Provider and OpenstackProvider when converting to Json and back.
I would like to be able to give choices in "Provider", and if those choices don't fit the ones available then it should not validate.
My attempt at this can be seen in the following code:
import spray.json._
import DefaultJsonProtocol._
case class Credentials(username: String, password: String)
abstract class Provider
case class Ec2Provider(endpoint: String,credentials: Credentials) extends Provider
case class OpenstackProvider(credentials: Credentials) extends Provider
case class Infrastructure(name: String, provider: Provider, availableInstanceTypes: List[String])
case class InfrastructuresList(infrastructures: List[Infrastructure])
object Infrastructures extends App with DefaultJsonProtocol {
implicit val credFormat = jsonFormat2(Credentials)
implicit val ec2Provider = jsonFormat2(Ec2Provider)
implicit val novaProvider = jsonFormat1(OpenstackProvider)
implicit val infraFormat = jsonFormat3(Infrastructure)
implicit val infrasFormat = jsonFormat1(InfrastructuresList)
println(
InfrastructuresList(
List(
Infrastructure("test", Ec2Provider("nova", Credentials("user","pass")), List("1", "2"))
)
).toJson
)
}
Unfortunately, it fails because it can not find a formatter for Provider abstract class.
test.scala:19: could not find implicit value for evidence parameter of type Infrastructures.JF[Provider]
Anyone have any solution for this?
What you want to do is not available out of the box (i.e. via something like type hints that allow the deserializer to know what concrete class to instantiate), but it's certainly possible with a little leg work. First, the example, using a simplified version of the code you posted above:
case class Credentials(user:String, password:String)
abstract class Provider
case class Ec2Provider(endpoint:String, creds:Credentials) extends Provider
case class OpenstackProvider(creds:Credentials) extends Provider
case class Infrastructure(name:String, provider:Provider)
object MyJsonProtocol extends DefaultJsonProtocol{
implicit object ProviderJsonFormat extends RootJsonFormat[Provider]{
def write(p:Provider) = p match{
case ec2:Ec2Provider => ec2.toJson
case os:OpenstackProvider => os.toJson
}
def read(value:JsValue) = value match{
case obj:JsObject if (obj.fields.size == 2) => value.convertTo[Ec2Provider]
case obj:JsObject => value.convertTo[OpenstackProvider]
}
}
implicit val credFmt = jsonFormat2(Credentials)
implicit val ec2Fmt = jsonFormat2(Ec2Provider)
implicit val openStackFmt = jsonFormat1(OpenstackProvider)
implicit val infraFmt = jsonFormat2(Infrastructure)
}
object PolyTest {
import MyJsonProtocol._
def main(args: Array[String]) {
val infra = List(
Infrastructure("ec2", Ec2Provider("foo", Credentials("me", "pass"))),
Infrastructure("openstack", OpenstackProvider(Credentials("me2", "pass2")))
)
val json = infra.toJson.toString
val infra2 = JsonParser(json).convertTo[List[Infrastructure]]
println(infra == infra2)
}
}
In order to be able to serialize/deserialize instances of the abstract class Provider, I've created a custom formatter where I am supplying operations for reading and writing Provider instances. All I'm doing in these functions though is checking a simple condition (binary here as there are only 2 impls of Provider) to see what type it is and then delegating to logic to handle that type.
For writing, I just need to know which instance type it is which is easy. Reading is a little trickier though. For reading, I'm checking how many properties the object has and since the two impls have different numbers of props, I can differentiate which is which this way. The check I'm making here is very rudimentary, but it shows the point that if you can look at the Json AST and differentiate the types, you can then pick which one to deserialize to. Your actual check can be as simple or as complicated as you like, as long as it's is deterministic in differentiating the types.