Maria Livia
Maria Livia

Reputation: 75

Unable to convert Scrooge generated classes to maps using Shapeless

I'm using Scrooge to generate classes. They look something like this, here's an example:

object Flags extends ThriftStructCodec3[Flags] {
  private val NoPassthroughFields = immutable$Map.empty[Short, TFieldBlob]
  val Struct = new TStruct("Flags")
  val IsDangerousField = new TField("isDangerous", TType.BOOL, 1)
  val IsDangerousFieldManifest = implicitly[Manifest[Boolean]]
  val IsWildField = new TField("isWild", TType.BOOL, 2)
  val IsWildFieldManifest = implicitly[Manifest[Boolean]]

  def apply(
    isDangerous: Option[Boolean] = None,
    isWild: Option[Boolean] = None
  ): Flags =
    new Immutable(
      isDangerous,
      isWild
    )

  def unapply(_item: Flags): Option[scala.Product2[Option[Boolean], Option[Boolean]]] = Some(_item)

  object Immutable extends ThriftStructCodec3[Flags] {
    override def encode(_item: Flags, _oproto: TProtocol) { _item.write(_oproto) }
    override def decode(_iprot: TProtocol): Flags = Flags.decode(_iprot)
  }

  /**
   * The default read-only implementation of Flags.  You typically should not need to
   * directly reference this class; instead, use the Flags.apply method to construct
   * new instances.
   */
  class Immutable(
    val isDangerous: Option[Boolean],
    val isWild: Option[Boolean],
    override val _passthroughFields: immutable$Map[Short, TFieldBlob]
  ) extends Flags {
    def this(
      isDangerous: Option[Boolean] = None,
      isWild: Option[Boolean] = None
    ) = this(
      isDangerous,
      isWild,
      Map.empty
    )
  }
}

trait Flags
  extends ThriftStruct
  with scala.Product2[Option[Boolean], Option[Boolean]]
  with java.io.Serializable
{
  import Flags._

  def isDangerous: Option[Boolean]
  def isWild: Option[Boolean]

  def _passthroughFields: immutable$Map[Short, TFieldBlob] = immutable$Map.empty

  def _1 = isDangerous
  def _2 = isWild

  override def productArity: Int = 2

  override def productElement(n: Int): Any = n match {
    case 0 => this.isDangerous
    case 1 => this.isWild
    case _ => throw new IndexOutOfBoundsException(n.toString)
  }

  override def productPrefix: String = "Flags"
}

The aim is to generate from these classes a nested map using Shapeless, do some operation on it and then convert the map back to thrift generated classes.

Using Converting nested case classes to nested Maps using Shapeless and Converting Map[String,Any] to a case class using Shapeless I managed to put together a project which works fine with basic case classes. On top of the examples provided in the stack overflow responses I have also implemented implicits for Option[A].

Now, because Scrooge is not generating proper case classes I was trying to use the Immutable class instead (e.g. Flags.Immutable). Doing this means I have to define implicits for all the thrift classes to convert them to NameOfClass.Immutable. So far so good.

But I have 2 things I seem to not get right (the full implementation can be found in the ClassToMap.scala file):

(1) When generating the map from thrift classes the Option implicit seems to be ignored.

implicit def hconsToMapRecOption[K <: Symbol, V, R <: HList, T <: HList]
    (implicit
     wit: Witness.Aux[K],
     gen: LabelledGeneric.Aux[V, R],
     tmrT: Lazy[ToMapRec[T]],
     tmrH: Lazy[ToMapRec[R]]
    ): ToMapRec[FieldType[K, Option[V]] :: T] = new ToMapRec[FieldType[K, Option[V]] :: T] {
      override def apply(l: FieldType[K, Option[V]] :: T): Map[String, Any] = {
        tmrT.value(l.tail) + (wit.value.name -> l.head.map(value => tmrH.value(gen.to(value))))
      }
    }

(2) When dealing with thrift unions my implicit is not being used.

implicit def hconsToMapRecGoatData[K <: Symbol, R <: HList, T <: HList]
    (implicit
     wit: Witness.Aux[K],
     gen: LabelledGeneric.Aux[MyGoat, R],
     tmrT: Lazy[ToMapRec[T]],
     tmrH: Lazy[ToMapRec[R]]
    ): ToMapRec[FieldType[K, MyGoatData] :: T] = new ToMapRec[FieldType[K, MyGoatData] :: T] {
      override def apply(l: FieldType[K, MyGoatData] :: T): Map[String, Any] = {
        tmrT.value(l.tail) + (wit.value.name -> tmrH.value(gen.to(l.head.goat)))
      }
    }

I haven't spent so much time looking at the map to class implementation, but I assume it should mirror the class to map one.

Upvotes: 4

Views: 308

Answers (0)

Related Questions