使用无变形从案例类中得出查询字符串参数

问题描述 投票:2回答:2

我正在尝试派生用于将案例类序列化为查询字符串的类型类。但是有一个不同之处-列表不是按常规方式编码的(据我所知,“常规”方式是什么),而是像下面这样,并结合了列表的字段名称。

case class Example(attributes: List[String])
val example = Example(List("foo", "bar"))

encode(example) // attributes.1=foo&attributes.2=bar

我有一个非常基本的东西适用于基元,现在我需要一些使列表按预期方式工作的方法的想法。

trait Encoder[T] {
  def encode(value: T): String
}

object Encoder {
  def apply[T](implicit encoder: Encoder[T]): Encoder[T] = encoder
}

def createEncoder[A](fn: A => String): Encoder[A] =
  (value: A) => fn(value)

implicit def hlistEncoder[K <: Symbol, H, T <: HList](
    implicit
    witness: Witness.Aux[K],
    hEncoder: Lazy[Encoder[H]],
    tEncoder: Encoder[T]
): Encoder[FieldType[K, H] :: T] = {
  val fieldName: String = witness.value.name

  createEncoder { hlist =>
    val head = hEncoder.value.encode(hlist.head)
    hlist.tail match {
      case HNil => s"$fieldName=$head"
      case _ =>
        val tail = tEncoder.encode(hlist.tail)
        s"$fieldName=$head&$tail"
    }
  }
}

implicit def genericEncoder[A, H](
    implicit
    generic: LabelledGeneric.Aux[A, H],
    hEncoder: Lazy[Encoder[H]]
): Encoder[A] =
  createEncoder { value =>
    hEncoder.value.encode(generic.to(value))
  }

implicit val intEncoder: Encoder[Int] = createEncoder(_.toString)
implicit val strEncoder: Encoder[String] = createEncoder(identity)
implicit val boolEncoder: Encoder[Boolean] = createEncoder(_.toString)
implicit val hnilEncoder: Encoder[HNil] = createEncoder(_ => "")

谢谢!

scala typeclass shapeless
2个回答
1
投票

此案例很好地描述了Type Astronaut书:https://books.underscore.io/shapeless-guide/shapeless-guide.html#records-and-labelledgeneric

因此,基本上,您需要看一下LabeledGeneric类型类-它对任何产品类型(例如案例类)派生都有帮助。

在您的情况下,实现可能看起来像:

  import shapeless.{::, HList, HNil, LabelledGeneric, Lazy, Witness}
  import shapeless.labelled.FieldType

  trait Encoder[T] {
    def encode(value: T): String
  }

  object Encoder {
    def createEncoder[T](f: T => String): Encoder[T] = f(_)
    def apply[T](implicit encoder: Encoder[T]): Encoder[T] = encoder

    implicit val stringEncoder: Encoder[String] = createEncoder[String](identity)
    implicit val intEncoder: Encoder[Int] = createEncoder[Int](_.toString)
  }

  // Special encoder, which can encode product or object
  trait ObjectEncoder[T] extends Encoder[T] {
    final override def encode(value: T): String = {
      encodeObject(value).map{
        case (key, value) => s"$key=$value"
      }.mkString("&")
    }

    def encodeObject(t: T): Map[String, String]
  }

  object ObjectEncoder {
    def createEncoder[T](f: T => Map[String, String]): ObjectEncoder[T] = f(_)
    def apply[T](implicit encoder: ObjectEncoder[T]): ObjectEncoder[T] = encoder

    // This need to terminate derivation process
    implicit val hNilObjectEncoder: ObjectEncoder[HNil] = {
      ObjectEncoder.createEncoder(_ => Map.empty)
    }

    // Generate object encoder derivation
    implicit def hlistObjectEncoder[K <: Symbol, H, T <: HList]
    (implicit
     fieldWitness: Witness.Aux[K],
     headEncoder: Lazy[Encoder[H]],
     tailEncoder: ObjectEncoder[T]
    ): ObjectEncoder[FieldType[K, H] :: T] = {
      val fieldName: String = fieldWitness.value.name
      ObjectEncoder.createEncoder { hlist =>
        val headValue: String = headEncoder.value.encode(hlist.head)
        val head: Map[String, String] = List(fieldName -> headValue).toMap
        val tail: Map[String, String] = tailEncoder.encodeObject(hlist.tail)
        head ++ tail
      }
    }

    implicit def genericObjectEncoder[A, H]
    ( implicit
      generic: LabelledGeneric.Aux[A, H],
      hEncoder: Lazy[ObjectEncoder[H]]
    ): ObjectEncoder[A] = {
      ObjectEncoder.createEncoder { value =>
        val t = generic.to(value)
        hEncoder.value.encodeObject(t)
      }
    }
  }

  // Just syntactic sugar helpers
  object EncoderSyntax {
    implicit class EncodeOps[A](a: A) {
      def encode(implicit encoder: Encoder[A]): String = encoder.encode(a)
    }
  }

  def main(args: Array[String]): Unit = {
    import Encoder._
    import ObjectEncoder._
    import EncoderSyntax._

    case class Example(foo: String, bar: String)
    val example = Example("foo", "bar")
    println(example.encode)
  }

将产生下一个结果:

foo=foo&bar=bar

希望这会有所帮助!


0
投票

我最终通过修改typeclass来解决此问题,以返回一个在应用时构造正确字符串的函数。它不是完美的,但我很确定它能解决问题。

trait Encoder[T] {
  def encode(value: T): String => List[String]
}

object Encoder {
  def createEncoder[T](f: T => String): Encoder[T] = value => name => List(s"$name=${f(value)}")
  def apply[T](implicit encoder: Encoder[T]): Encoder[T] = encoder

  implicit val stringEncoder: Encoder[String] = createEncoder[String](identity)
  implicit val intEncoder: Encoder[Int] = createEncoder[Int](_.toString)
  implicit val boolEncoder: Encoder[Boolean] = createEncoder(_.toString)
  implicit def listEncoder[T](implicit encoder: Encoder[T]): Encoder[List[T]] =
    (list: List[T]) => (name: String) => list.zipWithIndex.map { case (value, index) => s"$name.${index + 1}=$value" }

  implicit def mapEncoder[T](implicit encoder: Encoder[T]): Encoder[Map[String, T]] =
    (map: Map[String, T]) =>
      (name: String) =>
        map.zipWithIndex.flatMap {
          case ((key, value), index) => List(s"$name.${index + 1}.Name=$key", s"$name.${index + 1}.Value=$value")
        }.toList
}

trait ObjectEncoder[T] extends Encoder[T] {
  final override def encode(value: T): String => List[String] =
    name => encodeObject(value).apply(name)

  def encodeObject(t: T): String => List[String]
}

object ObjectEncoder {
  def createEncoder[T](f: T => String => List[String]): ObjectEncoder[T] = new ObjectEncoder[T] {
    override def encodeObject(t: T): String => List[String] = name => f.apply(t).apply(name)
  }
  def apply[T](implicit encoder: ObjectEncoder[T]): ObjectEncoder[T] = encoder

  implicit val hNilObjectEncoder: ObjectEncoder[HNil] = createEncoder(_ => _ => List.empty)

  implicit def hlistObjectEncoder[K <: Symbol, H, T <: HList](
      implicit
      fieldWitness: Witness.Aux[K],
      headEncoder: Lazy[Encoder[H]],
      tailEncoder: ObjectEncoder[T]): ObjectEncoder[FieldType[K, H] :: T] = {
    val fieldName: String = fieldWitness.value.name

    createEncoder { hlist =>
      val head: String => List[String] = headEncoder.value.encode(hlist.head)
      val tail: String => List[String] = tailEncoder.encodeObject(hlist.tail)
      (name: String) =>
        head.apply(fieldName) ++ tail.apply(name)
    }
  }

  implicit def genericObjectEncoder[A, H](
      implicit
      generic: LabelledGeneric.Aux[A, H],
      hEncoder: Lazy[ObjectEncoder[H]]): ObjectEncoder[A] =
    ObjectEncoder.createEncoder { value =>
      val t = generic.to(value)
      hEncoder.value.encodeObject(t)
    }

  // Unfortunately we must apply the returned function with an empty string, the arg is used for formatting
  // each piece but is unecessary for the whole thing
  def encode[T](input: T)(implicit encoder: Encoder[T]) = encoder.encode(input).apply("").mkString("&")
}
© www.soinside.com 2019 - 2024. All rights reserved.