lift-json extraction fails on Map with algebraic data structure

84 views
Skip to first unread message

Miguel Negrão

unread,
Jul 26, 2011, 11:05:44 AM7/26/11
to lif...@googlegroups.com
Hi

I have an algebraic data structure,  SingleOrVector. I've made a serializer for it and it works fine if it's not inside a Map, but if I put it inside a map I get an error. Any idea what causes this ? *

Also, wouldn't it be possible to automate the serialization of algebraic data structures defined with a sealed trait and children case classes ?

thanks
Miguel Negrão

*
import net.liftweb.json.Serialization._
import net.liftweb.json._

object Test {

  class SingleOrVectorSerializer extends Serializer[SingleOrVector[Double]] {
    private val singleOrVectorClass = classOf[SingleOrVector[Double]]

    def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), SingleOrVector[Double]] = {
      case (TypeInfo(`singleOrVectorClass`, _), json) => json match {
        case JObject( List(JField("val", JDouble(x:Double)) ) ) => SingleValue(x)
        case JObject( List(JField("val", JArray(x:List[JDouble])) ) ) => VectorValue( x.map(_.num).toIndexedSeq )
        case x => throw new MappingException("Can't convert " + x + " to SingleOrVector")
      }
    }

    def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
      case SingleValue(x:Double) => JObject( List(JField("val", JDouble(x)) ) )
      case VectorValue(x:Vector[Double]) => JObject( List( JField("val", JArray( x.toList.map(JDouble(_)) ) ) ) )
    }
  }


  implicit val formats = DefaultFormats +  new SingleOrVectorSerializer


  sealed trait SingleOrVector[A]
  case class SingleValue[A](value:A) extends SingleOrVector[A]
  case class VectorValue[A](value:IndexedSeq[A]) extends SingleOrVector[A]

  case class A(a:Map[String,SingleOrVector[Double]])
  case class B(b:SingleOrVector[Double])
  def main(args: Array[String]) {

    val jsonString1 =  write( B(SingleValue(2.0)) )
    val x = read[B](jsonString1) //this works.
    println(x)

    val jsonString2 =  write( A(Map("hello"->SingleValue(2.0))) )
    val y = read[A](jsonString2) //this fails...
    println(y) 

  }
}

Error:

Exception in thread "main" net.liftweb.json.MappingException: unknown error
at net.liftweb.json.Extraction$.extract(Extraction.scala:45)
at net.liftweb.json.JsonAST$JValue.extract(JsonAST.scala:290)
at net.liftweb.json.Serialization$.read(Serialization.scala:48)
at Test$.main(testjsonbug2.scala:48)
at Test.main(testjsonbug2.scala)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.lang.IndexOutOfBoundsException: 0
at scala.collection.LinearSeqOptimized$class.apply(LinearSeqOptimized.scala:51)
at scala.collection.immutable.List.apply(List.scala:45)
at net.liftweb.json.ScalaSigReader$.findPrimitive$1(ScalaSig.scala:68)
at net.liftweb.json.ScalaSigReader$.findArgType(ScalaSig.scala:74)
at net.liftweb.json.ScalaSigReader$.readConstructor(ScalaSig.scala:26)
at net.liftweb.json.Meta$$anonfun$2.apply(Meta.scala:114)
at net.liftweb.json.Meta$$anonfun$2.apply(Meta.scala:112)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)
at scala.collection.immutable.List.foreach(List.scala:45)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:194)
at scala.collection.immutable.List.map(List.scala:45)
at net.liftweb.json.Meta$.parameterizedTypeOpt$1(Meta.scala:112)
at net.liftweb.json.Meta$.mkConstructor$1(Meta.scala:123)
at net.liftweb.json.Meta$.fieldMapping$1(Meta.scala:139)
at net.liftweb.json.Meta$.mkContainer$1(Meta.scala:106)
at net.liftweb.json.Meta$.fieldMapping$1(Meta.scala:135)
at net.liftweb.json.Meta$.toArg$1(Meta.scala:153)
at net.liftweb.json.Meta$$anonfun$constructors$1$1$$anonfun$apply$1.apply(Meta.scala:98)
at net.liftweb.json.Meta$$anonfun$constructors$1$1$$anonfun$apply$1.apply(Meta.scala:97)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)
at scala.collection.immutable.List.foreach(List.scala:45)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:194)
at scala.collection.immutable.List.map(List.scala:45)
at net.liftweb.json.Meta$$anonfun$constructors$1$1.apply(Meta.scala:97)
at net.liftweb.json.Meta$$anonfun$constructors$1$1.apply(Meta.scala:96)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)
at scala.collection.immutable.List.foreach(List.scala:45)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:194)
at scala.collection.immutable.List.map(List.scala:45)
at net.liftweb.json.Meta$.constructors$1(Meta.scala:96)
at net.liftweb.json.Meta$$anonfun$mappingOf$1.apply(Meta.scala:164)
at net.liftweb.json.Meta$$anonfun$mappingOf$1.apply(Meta.scala:159)
at net.liftweb.json.Meta$Memo.memoize(Meta.scala:198)
at net.liftweb.json.Meta$.mappingOf(Meta.scala:159)
at net.liftweb.json.Extraction$.mkMapping$1(Extraction.scala:192)
at net.liftweb.json.Extraction$.net$liftweb$json$Extraction$$extract0(Extraction.scala:194)
at net.liftweb.json.Extraction$.extract(Extraction.scala:42)
... 9 more

Process finished with exit code 1

Joni Freeman

unread,
Jul 27, 2011, 7:09:54 AM7/27/11
to lif...@googlegroups.com
Hi,

Looks like this is another bug in Scala 2.9 version (Scala 2.9 no longer
fully works with Java reflection, lift-json-2.4 uses parses required
information from ScalaSig annotation), the code below works on 2.8.
Could you please file a ticket and assign to me.

Serialization of algebraic sum types is automated but requires use of a
type hint (an extra field added to JSON):

implicit val formats =
DefaultFormats + FullTypeHints(classOf[SingleOrVector[_]] :: Nil)

Cheers Joni

> at net.liftweb.json.Meta$$anonfun$constructors$1
> $1.apply(Meta.scala:97)
> at net.liftweb.json.Meta$$anonfun$constructors$1
> $1.apply(Meta.scala:96)
> at scala.collection.TraversableLike$$anonfun$map
> $1.apply(TraversableLike.scala:194)
> at scala.collection.TraversableLike$$anonfun$map
> $1.apply(TraversableLike.scala:194)
> at scala.collection.LinearSeqOptimized


> $class.foreach(LinearSeqOptimized.scala:59)
> at scala.collection.immutable.List.foreach(List.scala:45)
> at scala.collection.TraversableLike

> $class.map(TraversableLike.scala:194)
> at scala.collection.immutable.List.map(List.scala:45)
> at net.liftweb.json.Meta$.constructors$1(Meta.scala:96)
> at net.liftweb.json.Meta$$anonfun$mappingOf$1.apply(Meta.scala:164)
> at net.liftweb.json.Meta$$anonfun$mappingOf$1.apply(Meta.scala:159)
> at net.liftweb.json.Meta$Memo.memoize(Meta.scala:198)
> at net.liftweb.json.Meta$.mappingOf(Meta.scala:159)
> at net.liftweb.json.Extraction$.mkMapping$1(Extraction.scala:192)
> at net.liftweb.json.Extraction$.net$liftweb$json$Extraction
> $$extract0(Extraction.scala:194)
> at net.liftweb.json.Extraction$.extract(Extraction.scala:42)
> ... 9 more
>
>
> Process finished with exit code 1
>

> --
> You received this message because you are subscribed to the Google
> Groups "Lift" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/liftweb/-/RzxvFlb0uhgJ.
> To post to this group, send email to lif...@googlegroups.com.
> To unsubscribe from this group, send email to liftweb
> +unsub...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/liftweb?hl=en.


Miguel Negrão

unread,
Jul 27, 2011, 8:03:37 AM7/27/11
to lif...@googlegroups.com
Hi Joni,


On Wednesday, July 27, 2011 1:09:54 PM UTC+2, Joni Freeman wrote:
Hi,

Looks like this is another bug in Scala 2.9 version (Scala 2.9 no longer
fully works with Java reflection, lift-json-2.4 uses parses required
information from ScalaSig annotation), the code below works on 2.8.
Could you please file a ticket and assign to me.

Ticket created. 

Serialization of algebraic sum types is automated but requires use of a
type hint (an extra field added to JSON):

implicit val formats =
  DefaultFormats + FullTypeHints(classOf[SingleOrVector[_]] :: Nil)


Ah, great, that's less work for me ! I have other cases where I'm making a serializer object in order to convert lists that I have in my case classes to the right collection type that use, most usually IndexedSeq. Is this the best way to do it ? Example :

case class SoundFileBuf(path:String, bookmarks: IndexedSeq[SoundFileBuf.Bookmark] = IndexedSeq.empty)

 class SoundFileBufSerializer extends Serializer[SoundFileBuf] {
    private val soundFileBufClass = classOf[SoundFileBuf]

    def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), SoundFileBuf] = {
      case (TypeInfo(`soundFileBufClass`, _), json) => json match {
        case JObject(List(
        JField("path", JString(path)),
        JField("bookmarks", JArray( iseq ) )
        )) => SoundFileBuf(path,iseq.map(_.extract[Bookmark]).toIndexedSeq)
        case x => throw new MappingException("Can't convert " + x + " to SoundFileBuf")
      }
    }

    def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
      case SoundFileBuf(path, iseq) => JObject(List(
        JField("path",JString(path)),
        JField("bookmarks", JArray( iseq.toList.map(Extraction.decompose(_)) ) )
      ))
    }
  }

 best,
Miguel Negrão

Joni Freeman

unread,
Jul 28, 2011, 6:58:40 AM7/28/11
to lif...@googlegroups.com
Hi,

On Wed, 2011-07-27 at 05:03 -0700, Miguel Negrão wrote:
> Ah, great, that's less work for me ! I have other cases where I'm
> making a serializer object in order to convert lists that I have in my
> case classes to the right collection type that use, most usually
> IndexedSeq. Is this the best way to do it ? Example :

Unfortunately that's what you need to do now. I filed an enhancement
ticket which should make your life easier:

http://www.assembla.com/spaces/liftweb/tickets/1079-improved-support-for-custom-collections-in-lift-json

After that it is possible to add support for IndexedSeq using a custom
serializer.

Cheers Joni


Reply all
Reply to author
Forward
0 new messages