[2.0.2-scala] ParsingException: Unable to find a case accessor for [case class with nested case class]

507 views
Skip to first unread message

Jxtps

unread,
Aug 8, 2012, 5:08:18 PM8/8/12
to play-fr...@googlegroups.com
I'm running into a problem deserializing nested case classes with Jerkson in Play.
case class Inner(foo: String, bar: Int)
case class Outer(hello: String, world: Inner)

object Application extends Controller {

  def index = Action {

    Logger.info("--------")
    val o = Outer("adam", Inner("eve", 4))
    Logger.info(o.toString)
    val oJson = com.codahale.jerkson.Json.generate(o)
    Logger.info(oJson)
    val o2 = com.codahale.jerkson.Json.parse[Outer](oJson)
    Logger.info(o2.toString)
    val o2Json = com.codahale.jerkson.Json.generate(o2)
    Logger.info(o2Json)
    Logger.info("--------")
 

    Ok("ok")

  }
}
 The bolded red line causes an exception:
 
2012-08-08 13:28:13,293  INFO  application - --------
2012-08-08 13:28:13,295  INFO  application - Outer(adam,Inner(eve,4))
2012-08-08 13:28:13,297  INFO  application - {"hello":"adam","world":{"foo":"eve","bar":4}}
2012-08-08 13:28:13,326  ERROR  application -
! @6ba4i1pig - Internal server error, for request [GET /] ->
play.core.ActionInvoker$$anonfun$receive$1$$anon$1: Execution exception [[ParsingException: Unable to find a case access
or for controllers.Outer]]
        at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:134) [play_2.9.1.jar:2.0.2]
        at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:115) [play_2.9.1.jar:2.0.2]
        at akka.actor.Actor$class.apply(Actor.scala:318) [akka-actor.jar:2.0.2]
        at play.core.ActionInvoker.apply(Invoker.scala:113) [play_2.9.1.jar:2.0.2]
        at akka.actor.ActorCell.invoke(ActorCell.scala:626) [akka-actor.jar:2.0.2]
        at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:197) [akka-actor.jar:2.0.2]
        at akka.dispatch.Mailbox.run(Mailbox.scala:179) [akka-actor.jar:2.0.2]
        at akka.dispatch.ForkJoinExecutorConfigurator$MailboxExecutionTask.exec(AbstractDispatcher.scala:516) [akka-acto
r.jar:2.0.2]
        at akka.jsr166y.ForkJoinTask.doExec(ForkJoinTask.java:259) [akka-actor.jar:2.0.2]
        at akka.jsr166y.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:975) [akka-actor.jar:2.0.2]
        at akka.jsr166y.ForkJoinPool.runWorker(ForkJoinPool.java:1479) [akka-actor.jar:2.0.2]
        at akka.jsr166y.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:104) [akka-actor.jar:2.0.2]
Caused by: com.codahale.jerkson.ParsingException: Unable to find a case accessor for controllers.Outer
        at com.codahale.jerkson.ParsingException$.apply(ParsingException.scala:17) ~[jerkson_2.9.1.jar:na]
        at com.codahale.jerkson.Parser$class.parse(Parser.scala:86) ~[jerkson_2.9.1.jar:na]
        at com.codahale.jerkson.Json$.parse(Json.scala:6) ~[jerkson_2.9.1.jar:na]
        at com.codahale.jerkson.Parser$class.parse(Parser.scala:14) ~[jerkson_2.9.1.jar:na]
        at com.codahale.jerkson.Json$.parse(Json.scala:6) ~[jerkson_2.9.1.jar:na]
        at controllers.Application$$anonfun$index$1.apply(Application.scala:24) ~[classes/:2.0.2]
        at controllers.Application$$anonfun$index$1.apply(Application.scala:18) ~[classes/:2.0.2]
        at play.api.mvc.Action$$anonfun$apply$4.apply(Action.scala:204) ~[play_2.9.1.jar:2.0.2]
        at play.api.mvc.Action$$anonfun$apply$4.apply(Action.scala:204) ~[play_2.9.1.jar:2.0.2]
        at play.api.mvc.Action$$anon$1.apply(Action.scala:170) ~[play_2.9.1.jar:2.0.2]
        at play.core.ActionInvoker$$anonfun$receive$1$$anonfun$6.apply(Invoker.scala:126) ~[play_2.9.1.jar:2.0.2]
        at play.core.ActionInvoker$$anonfun$receive$1$$anonfun$6.apply(Invoker.scala:126) ~[play_2.9.1.jar:2.0.2]
        at play.utils.Threads$.withContextClassLoader(Threads.scala:17) ~[play_2.9.1.jar:2.0.2]
        at play.core.ActionInvoker$$anonfun$receive$1.apply(Invoker.scala:125) [play_2.9.1.jar:2.0.2]
        ... 11 common frames omitted
Caused by: org.codehaus.jackson.map.JsonMappingException: Unable to find a case accessor for controllers.Outer
        at com.codahale.jerkson.deser.CaseClassDeserializer$$anonfun$4.apply(CaseClassDeserializer.scala:39) ~[jerkson_2.9.1.jar:na]
        at com.codahale.jerkson.deser.CaseClassDeserializer$$anonfun$4.apply(CaseClassDeserializer.scala:39) ~[jerkson_2.9.1.jar:na]
        at scala.Option.getOrElse(Option.scala:108) ~[scala-library.jar:0.11.3]
        at com.codahale.jerkson.deser.CaseClassDeserializer.<init>(CaseClassDeserializer.scala:39) ~[jerkson_2.9.1.jar:na]
        at com.codahale.jerkson.deser.ScalaDeserializers.findBeanDeserializer(ScalaDeserializers.scala:94) ~[jerkson_2.9.1.jar:na]
        at org.codehaus.jackson.map.deser.BeanDeserializerFactory._findCustomBeanDeserializer(BeanDeserializerFactory.java:482) ~[jackson-mapper-asl.jar:1.9.7]
        at org.codehaus.jackson.map.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:599) ~[jackson-mapper-asl.jar:1.9.7]
        at org.codehaus.jackson.map.deser.StdDeserializerProvider._createDeserializer(StdDeserializerProvider.java:401)~[jackson-mapper-asl.jar:1.9.7]
        at org.codehaus.jackson.map.deser.StdDeserializerProvider._createAndCache2(StdDeserializerProvider.java:310) ~[jackson-mapper-asl.jar:1.9.7]
        at org.codehaus.jackson.map.deser.StdDeserializerProvider._createAndCacheValueDeserializer(StdDeserializerProvider.java:290) ~[jackson-mapper-asl.jar:1.9.7]
        at org.codehaus.jackson.map.deser.StdDeserializerProvider.findValueDeserializer(StdDeserializerProvider.java:159) ~[jackson-mapper-asl.jar:1.9.7]
        at org.codehaus.jackson.map.deser.StdDeserializerProvider.findTypedValueDeserializer(StdDeserializerProvider.java:180) ~[jackson-mapper-asl.jar:1.9.7]
        at org.codehaus.jackson.map.ObjectMapper._findRootDeserializer(ObjectMapper.java:2829) ~[jackson-mapper-asl.jar:1.9.7]
        at org.codehaus.jackson.map.ObjectMapper._readValue(ObjectMapper.java:2699) ~[jackson-mapper-asl.jar:1.9.7]
        at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1315) ~[jackson-mapper-asl.jar:1.9.7]
        at com.codahale.jerkson.Parser$class.parse(Parser.scala:83) ~[jerkson_2.9.1.jar:na]
        ... 23 common frames omitted
There's been some chatter in the past about Play and Jerkson. See:
 
Has anyone else run into this problem and found a solution?
 
Note that "simple" case classes work, i.e. case class Simple(a:String, b:Int) serialize & deserialize fine.
 
I don't know the innards of Java/Scala well enough to know if this is an independent Jerkson, or a Jerkson-Play combination issue.
 
The exception stems from https://github.com/codahale/jerkson/blob/master/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala#L36 - it seems like the CaseClassDeserializer doesn't find the correct constructor, but there's probably more to it than that.
 

Jxtps

unread,
Aug 8, 2012, 5:12:07 PM8/8/12
to play-fr...@googlegroups.com
Logged an issue on Jerkson's github page: https://github.com/codahale/jerkson/issues/67
 

Jxtps

unread,
Aug 10, 2012, 1:53:59 PM8/10/12
to play-fr...@googlegroups.com
I've debugged this further as described in: https://github.com/codahale/jerkson/issues/67#issuecomment-7651665
 
 

It seems to be an interaction issue between play and jerkson. On the first load in dev mode (from a fresh start) it works fine. If there is then a recompilation involving the nested classes, then it barfs on subsequent loads.

I suspect this is because the classes are effectively refreshed / updated, but jerkson keeps a static cache of the JavaTypes (see https://github.com/codahale/jerkson/blob/master/src/main/scala/com/codahale/jerkson/Types.scala#L9 ) and then presumably the matching done around https://github.com/codahale/jerkson/blob/master/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala#L21 fails, but I'm not sure.

If that is the case, then a potential fix could be to clear the Jerkson Types.cachedTypes on each reload in development. However, Types is private[jerkson] and Types.cachedTypes is private, so this is a little tricky to do.

Haven't found a workaround - right now I have to restart the dev server on each change to pretty much any part of the code. :(

 
 

Jxtps

unread,
Sep 10, 2012, 12:39:48 PM9/10/12
to play-fr...@googlegroups.com
Not really. I ended up using Play's form parsing infrastructure to parse the JSON instead of using Jerkson.
 
This was made easier by creating an AutoMapper object that can generate a Mapping for an arbitrary case class - the effective code duplication with the style of form definitions showcased on http://www.playframework.org/documentation/2.0.3/ScalaForms would otherwise make this pretty prohibitive.
 
Would be happy to donate that code to the play project, but it's frankly a bit of a mess and not fully general - I haven't used Java/Scala reflection enough to know how to write something like that "properly".
 
But it works for the things I need at least, and it makes creating a form *a lot* more pleasant + both typo and type safe.
 
 
 

On Sunday, September 9, 2012 9:35:19 AM UTC-7, Eric Bowman wrote:
This is biting me to, but just with sbt, not using play.

I wrote some code to do what you described:

    val typesClass = Types.getClass
    val module = typesClass.getField("MODULE$").get(null)
    val cachedTypesField = module.getClass.getDeclaredField("cachedTypes")
    cachedTypesField.setAccessible(true)
    val cachedTypes = cachedTypesField.get(module).asInstanceOf[scala.collection.mutable.ConcurrentMap[_, _]]
    cachedTypes.clear()

...but that didn't solve the issue.

Did you ever resolve it?

Jxtps

unread,
Sep 10, 2012, 1:12:05 PM9/10/12
to play-fr...@googlegroups.com
On second thought it probably doesn't make sense for general consumption.
 
I think the spirit of the Play form handling is to be more customizable & "spelled out" than an AutoMapper can realistically make things (sure, you can go to town with annotations to achieve full generality, but then what's the point?).
 
Also, it's a bit different when parsing JSON since it's easily generated from the case class - when actually doing HTML forms, you typically have 3x field name duplication with the current Play structure: 1x in the case class, 1x in the form definition (type-safe, but typo-unsafe), and 1x in the html template (both type & typo unsafe).  
 
With JSON and AutoMapper I was able to bring that down to 1x, and for my forms (which are certainly not "general case" forms, but rather trivial case forms) it does go down to 2x.
 
You can't get rid of the HTML template type/o unsafety issues without completely changing the entire architecture of forms and it's not clear to me what such an alternate would look like if it's even possible.
 
So sure, AutoMapping makes a lot of things a lot easier, but it also hides how some things are done, and doesn't give you a clear path towards the truly complicated case. The current Play structure is verbose, but very explicit and very clear. I don't think the Play core team would like to introduce an AutoMapper given that tradeoff.
 

Sina Bahram

unread,
Sep 10, 2012, 12:45:32 PM9/10/12
to play-fr...@googlegroups.com

Can you share a bit more info on this? I’m currently writing a restful API which will be sitting on top of json, and I find myself wondering what the best way of validating the json is. the way Play does forms is rather elegant, so I was hoping something similar existed for json.

 

It sounds like your recent development is the closest thing there is?

 

Take care,

Sina

 

 

Website: www.SinaBahram.com

Twitter: @SinaBahram

--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To view this discussion on the web visit https://groups.google.com/d/msg/play-framework/-/ijZGwzexPcQJ.
To post to this group, send email to play-fr...@googlegroups.com.
To unsubscribe from this group, send email to play-framewor...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/play-framework?hl=en.

Jxtps

unread,
Sep 11, 2012, 7:59:52 PM9/11/12
to play-fr...@googlegroups.com
You can use JSON input for your forms - you "just" need to write out your mappings by hand as per the play docs.

Nilanjan

unread,
Nov 13, 2012, 8:39:46 PM11/13/12
to play-fr...@googlegroups.com
FYI, Seems to work with Play 2.0.4 I will try reproduce for Play 2.0.2

Nilanjan  
Reply all
Reply to author
Forward
0 new messages