3

I'm trying to write a generic custom serializer for Json4s that can handle Java enums, which have type T <: Enum[T]. To do this I want to use the Enum.valueOf method, which takes a class token also of type T <: Enum[T]. This is what I have so far:

class EnumSerializer[T <: Enum[T]](implicit m: Manifest[T]) extends Serializer[T] {

  val enumerationClass: Class[_ <: Enum[T]] = m.runtimeClass.asInstanceOf[Class[T]]

  def deserialize(implicit format: Formats) :  PartialFunction[(TypeInfo, JValue), T] = {
    case (t @ TypeInfo(enumerationClass, _), json) => {
      json match {
        case JString(value) => Enum.valueOf(enumerationClass, value.toUpperCase()).asInstanceOf[T]
        case value => throw new MappingException(s"Can't convert $value to $enumerationClass")
      }
    }
  }

  def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
    case i : Enum[T] => JString(i.name())
  }
}

But I get the following compilation error:

inferred type arguments [_0] do not conform to method valueOf's type parameter bounds [T <: Enum[T]]
case JString(value) => Enum.valueOf(enumerationClass, value.toUpperCase()).asInstanceOf[T]

I can't figure out how to get enumerationClass to have the correct type.

Ryan
  • 919
  • 11
  • 19

2 Answers2

3

enumerationClass in your deserialize method shadows the val enumerationClass defined outside of it. Your code is equivalent to:

case (t @ TypeInfo(a, _), json) => {
  json match {
    case JString(value) => Enum.valueOf(a, value.toUpperCase()).asInstanceOf[T]
    case value => throw new MappingException(s"Can't convert $value to $enumerationClass")
  }
}

Which isn't what you want: this will always match since you do not restrict the class. You need make enumerationClass a stable identifier, i.e. here make it upper-case. See this question and answer for more reading on this.

class EnumSerializer[T <: Enum[T]](implicit m: Manifest[T]) extends Serializer[T] {

  val EnumerationClass = m.runtimeClass.asInstanceOf[Class[T]]

  def deserialize(implicit format: Formats) :  PartialFunction[(TypeInfo, JValue), T] = {
    case (t @ TypeInfo(EnumerationClass, _), json) => {
      json match {
        case JString(value) => Enum.valueOf(EnumerationClass, value.toUpperCase()).asInstanceOf[T]
        case value => throw new MappingException(s"Can't convert $value to $enumerationClass")
      }
    }
  }

  ...
}
Community
  • 1
  • 1
gourlaysama
  • 11,240
  • 3
  • 44
  • 51
  • Thanks, I didn't even realize that capital letter variable names meant anything in Scala. I actually had it that way in the original code (which was based on something less generic someone else wrote) but thought it was just weird style so I changed it before adding the generic parts and then wondered why it didn't work :). – Ryan Jun 25 '13 at 00:19
0

Try declaring enumerationClass as:

val enumerationClass: Class[T] = m.runtimeClass.asInstanceOf[Class[T]]

You already know that the the runtimeClass of the Manifest is of type T, so I'm not sure why you were declaring it as val enumerationClass: Class[_ <: Enum[T]]. The Enum.valueOf can not work with a wildcarded type so that's why you were seeing that error.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95