Unwrapping persisted entities

166 views
Skip to first unread message

Matt Duncan

unread,
Jul 25, 2014, 3:57:06 PM7/25/14
to mapp...@googlegroups.com

I’m using json4s to serialize entities into JSON as my app is primarly REST based. json4s provides implicit serialization for case classes which is great as all of my domain objects are case classes.

I’m running into a problem though when I try to serialize an entity returned by my DB. When I try to serialize an entity returned from the DB (Game with SurrogateIntId) what I end up seeing:

  • Returned entity — {dao.GameEntity$$anon$2@12589}"Game(param a, param b, param c)"
  • My regular case class — {models.Game@12622}"Game(param a, param b, param c)"

json4s doesn’t know how to deserialize the returned entity because it isn’t a case class(or a class for that matter, it’s an object?). What I need is a way of unwrapping the companion object from GameEntity so that json4s can read the correct object. Or a way to programatically return a copy of the case class from the persisted entity — otherwise I’ll need to write and explicitly call some kind of getOriginal method in each of my case classes just so I can get the right object.

Is there any way to do this? Thanks

Konstantinos Kougios

unread,
Jul 25, 2014, 4:57:04 PM7/25/14
to mapp...@googlegroups.com
what's the code to serialize a class? Is there a json4s call that allows you to pass the class type, i.e. serialize(game,game.getClass) ?
--
You received this message because you are subscribed to the Google Groups "mapperdao" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mapperdao+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matt Duncan

unread,
Jul 28, 2014, 11:46:55 AM7/28/14
to mapp...@googlegroups.com, kostas....@googlemail.com
json4s uses implicit conversions between primitives to extract values from a case class or deserialize into a case class. It implicitly reads the class from the object it is applied to. There is no such method as serialize(game,game.getClass)

def write[A <: AnyRef](a: A)(implicit formats: Formats): String =
   
JsonMethods.mapper.writeValueAsString(Extraction.decompose(a)(formats))


The problem is that even when explicitly providing the class it still won't work -- evaluating someGame.getClass still returns dao.GametEntity$$anon and so does cast.

The few other popular JSON libraries I've researched (spray-jsonargonautjacks) all work the same way. They all include ways to implement custom serializers as well but that is a ton of boilerplate. I really just need a way to my classes back as POSOs -- I don't need the extra functionality mapperdao embues at the point I'm serializing them as the client app won't be using that information.

If all else fails Ill use something like

case class Game(name: String,publisher: String,website: String, gameType: GameType.Value, id: Int) {

 
def getClean:Game = new Game(name,publisher,website,gameType,id)
}

and just return a copy to serialize but I'd really like not to have to do this...

Kostas kougios

unread,
Jul 28, 2014, 12:02:43 PM7/28/14
to Matt Duncan, mapp...@googlegroups.com
but what about someGame.getClass.getSuperClass? Does that work?

Matt Duncan

unread,
Jul 28, 2014, 12:18:28 PM7/28/14
to mapp...@googlegroups.com, matt.d...@gmail.com, kostas....@googlemail.com
getSuperclass does return the correct the class (models.Game) but then using cast on the original object still returns dao.GameEntity$$anon

Matt Duncan

unread,
Jul 31, 2014, 3:07:56 PM7/31/14
to mapp...@googlegroups.com, matt.d...@gmail.com, kostas....@googlemail.com
A small update:

I was able to work around having to use copy() on any objects i wanted to serialize as well as set up custom serializing(for the entities I needed this for) all with minimal boilerplate and one point of access for configuration. It's not an ideal solution as it doesn't address the original problem, but it's a decent compromise.

class LinkObjectEntitySerializer[T: Manifest] extends CustomSerializer[Entity[Int, Persisted, Class[T]]](formats =>(
 
{PartialFunction.empty},{
 
case tu: TeamUser =>
   
implicit val formats: Formats = DefaultFormats
   
("Team" ->
     
("name" -> tu.team.name) ~
     
("id" -> tu.team.id) ~
     
("resource" -> "/team/") ~
     
("isCaptain" -> tu.isCaptain)) ~
   
("User" ->
     
("name" -> tu.user.globalHandle) ~
     
("id" -> tu.user.id) ~
     
("resource" -> "/user/") ~
     
("isCaptain" -> tu.isCaptain))
}
 
))

class EntitySerializer[T: Manifest] extends CustomSerializer[Entity[Int, Persisted, Class[T]]](formats =>(
 
{PartialFunction.empty},{
 
case g: Game =>
   
implicit val formats: Formats = DefaultFormats + new org.json4s.ext.EnumNameSerializer(GameType)
   
Extraction.decompose(g.copy())
 
case u : User =>
   
implicit val formats: Formats = DefaultFormats + new LinkObjectEntitySerializer
   
Extraction.decompose(u.copy())
 
case t : Team =>
   
implicit val formats: Formats = DefaultFormats + new LinkObjectEntitySerializer
   
Extraction.decompose(t.copy())
...
}


I created a CustomSerializer that wraps around the returned Entity object and gleans the class from generic type with an implicit manifest. Using this approach I use pattern matching on my domain objects(all case classes thankfully!) to determine what was passed in -- and I don't even have to create a new typed class for each entity because of the implicit! 

The only need for a separate serializer is in the event that you have non-primitives as a member of a case class being serialized because the serializer can't use itself to serialize. In this case you create a serializer(LinkObjectEntitySerializer in my case) for each basic class(IE one with only primitives) and then include it into the next serializer with objects that depend on those basic classes.

Konstantinos Kougios

unread,
Jul 31, 2014, 4:28:10 PM7/31/14
to Matt Duncan, mapp...@googlegroups.com
thanks for sharing this

btw, I am trying to create a test sbt project for json4s, but it can't resolve the dependencies, do they provide a build for scala 2.11 or 2.10?

Matt Duncan

unread,
Jul 31, 2014, 4:37:59 PM7/31/14
to mapp...@googlegroups.com, matt.d...@gmail.com
They provide both, but I am using 2.10 in my project with the jackson variant(instead of native) because it's faster.

        "org.json4s" % "json4s-jackson_2.10" % "3.2.10",
       
"org.json4s" % "json4s-ext_2.10" % "3.2.10",
       
"org.json4s" % "json4s-core_2.10" % "3.2.10",


 
def getClean:Game = new <span
...

Konstantinos Kougios

unread,
Jul 31, 2014, 5:23:55 PM7/31/14
to mapp...@googlegroups.com
Thanks, I've created this attached small project. The JsonSuite throws this error:

An exception or error caused a run to abort: test.JsonSuite and test.JsonSuite$$anonfun$1$$anon$1 disagree on InnerClasses attribute
java.lang.IncompatibleClassChangeError: test.JsonSuite and test.JsonSuite$$anonfun$1$$anon$1 disagree on InnerClasses attribute
    at java.lang.Class.getDeclaringClass0(Native Method)
    at java.lang.Class.getDeclaringClass(Class.java:1101)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$constructorsAndCompanion$3$$anonfun$13.apply(Reflector.scala:125)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$constructorsAndCompanion$3$$anonfun$13.apply(Reflector.scala:122)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:245)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:245)
    at scala.collection.mutable.ResizableArray$class.foreach(ResizableArray.scala:59)
    at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:48)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:245)
    at scala.collection.AbstractTraversable.map(Traversable.scala:104)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$constructorsAndCompanion$3.apply(Reflector.scala:122)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder$$anonfun$constructorsAndCompanion$3.apply(Reflector.scala:116)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:245)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:245)
    at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
    at scala.collection.mutable.WrappedArray.foreach(WrappedArray.scala:35)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:245)
    at scala.collection.AbstractTraversable.map(Traversable.scala:104)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder.constructorsAndCompanion(Reflector.scala:116)
    at org.json4s.reflect.Reflector$ClassDescriptorBuilder.result(Reflector.scala:156)
    at org.json4s.reflect.Reflector$.createDescriptor(Reflector.scala:50)
    at org.json4s.reflect.Reflector$$anonfun$describe$1.apply(Reflector.scala:44)
    at org.json4s.reflect.Reflector$$anonfun$describe$1.apply(Reflector.scala:44)
    at org.json4s.reflect.package$Memo.apply(package.scala:39)
    at org.json4s.reflect.Reflector$.describe(Reflector.scala:44)
    at org.json4s.Extraction$.decomposeObject$1(Extraction.scala:91)
    at org.json4s.Extraction$.internalDecomposeWithBuilder(Extraction.scala:180)
    at org.json4s.Extraction$.decomposeWithBuilder(Extraction.scala:67)
    at org.json4s.native.Serialization$.write(Serialization.scala:44)
    at org.json4s.native.Serialization$.write(Serialization.scala:38)
    at test.Json$.toJson(Json.scala:14)
    at test.JsonSuite$$anonfun$1.apply$mcV$sp(JsonSuite.scala:12)
    at test.JsonSuite$$anonfun$1.apply(JsonSuite.scala:11)
    at test.JsonSuite$$anonfun$1.apply(JsonSuite.scala:11)


Are you getting the same issue?

In fact notice the 2nd test in JsonSuite:

    test("getDeclaringClass") {
        val g=new Game("Game 1", 4) with IntId {
            val id=5
        }
        g.getClass.getDeclaringClass
    }

it is throwing the same exception!
--
json4s-sample.tar.gz
Message has been deleted

Matt Duncan

unread,
Aug 1, 2014, 10:15:10 AM8/1/14
to mapp...@googlegroups.com
It seems you've found a long-standing bug in Java! I found several instances of this popping up. From the Java bug report: Compiling and running a class with a local class that is only used via a subclass causes an IncompatibleClassChangeError

I modified your sample to run with using this instead and it works.

case class Game(name:String,players:Int)

//Using this in the test suite runs correctly
case class GameWithId(name: String, players: Int, id: Int) extends IntId

I suspect if you follow the workaround in the bug report you can make something more to your liking.
...
json4s-sample-modified.zip
Reply all
Reply to author
Forward
0 new messages