在 Scala 中,如何将隐式转换应用于集合的子类型?
In Scala, how to apply implicit conversion to subtypes of a collection?
我正在尝试使用 argonaut 将对象列表转换为 json。该列表包含不同类型的验证错误列表。例如,它可以包含“MissingParameter”或“InvalidParameter”或任何其他类型的实例。我已经为上面提到的 类 定义了 EncodeJson (argonaut) 编解码器。有什么办法可以使用 argonaut 将上述类型的列表转换为 json 吗?我的意思是,我可以实现以下目标吗?
List(new MissingParameter("name"), new InvalidParameter("email")).asJson
我没有用过argonaut,但我猜这里的问题是一个普遍的问题。这里列表的类型是List[Product with Serializable]
。而且编译器不知道如何将它序列化为 Json。
我建议你创建一个 Parameter 特征,用它扩展 类,并为 Parameter 编写一个序列化程序来检查所有具有模式匹配的类型:
注意:我使用的是play-json库,你应该适配argonaut。
import play.api.libs.json.{Writes, JsValue, Json}
import play.api.libs.json.Writes._
trait Parameter
case class MissingParameter (paramName: String) extends Parameter
case class InvalidParameter (paramName: String) extends Parameter
implicit val writes1 = Json.writes[MissingParameter]
implicit val writes2 = Json.writes[InvalidParameter]
implicit val implicitParamWrites = new Writes[Parameter] {
def writes(param: Parameter): JsValue = {
param match {
case missing: MissingParameter => Json.toJson(missing)
case invalid: InvalidParameter => Json.toJson(invalid)
}
}
}
val list: List[Parameter] = List( MissingParameter("p1"), InvalidParameter("i1") )
Json.toJson(list)
Feyyaz 的解决方案似乎是最佳解决方案。为了避免模式匹配每个可能的子类,我在父类中定义了一个契约,因此每个子类都必须提供一个编码器。不确定它是否完美,但它符合我的预期。我希望它能帮助正在寻找类似解决方案的人。
import argonaut._, Argonaut._
import scala.collection.TraversableLike
trait HasEncoder[T <: HasEncoder[T]] { self: T =>
def encoder: EncodeJson[T]
lazy val json = encoder.encode(self)
}
object HasEncoder {
implicit def listToRichHasEncoderList[A <: HasEncoder[_], Repr](coll: TraversableLike[A, Repr]): RichHasEncoderList[A, Repr] = new RichHasEncoderList[A, Repr](coll)
}
class RichHasEncoderList[A <: HasEncoder[_], Repr](coll: TraversableLike[A, Repr]) {
lazy val json = jArray(coll.toList.map(_.json))
}
在上面的解决方案中,argonaut 需要一个列表来构造一个 json 数组 (jArray),因此,我必须接受任何可遍历的内容并将其转换为列表。我不确定我是否可以改进那部分。
和一个测试用例(确保导入上述代码所在的包,以便隐式 'json' 值在列表中可用):
import argonaut._, Argonaut._
import org.scalatest.{Matchers, FlatSpec}
/**
* Created by jamesanto on 12/17/15.
*/
class HasEncoderTest extends FlatSpec with Matchers {
case class MissingParameter(name: String) extends HasEncoder[MissingParameter] {
override def encoder: EncodeJson[MissingParameter] = casecodec1(MissingParameter.apply, MissingParameter.unapply)("name")
}
case class InvalidParameter(name: String, expected: String, actual: String) extends HasEncoder[InvalidParameter] {
override def encoder: EncodeJson[InvalidParameter] = casecodec3(InvalidParameter.apply, InvalidParameter.unapply)("name", "expected", "actual")
}
it should "encode list of objects of classes that extend HasEncoder" in {
val list = List(MissingParameter("email"), InvalidParameter("dob", "DOB in yyyy/MM/dd format", "10/10/1985"))
list.json.nospaces should be ("""[{"name":"email"},{"name":"dob","expected":"DOB in yyyy/MM/dd format","actual":"10/10/1985"}]""")
}
}
我正在尝试使用 argonaut 将对象列表转换为 json。该列表包含不同类型的验证错误列表。例如,它可以包含“MissingParameter”或“InvalidParameter”或任何其他类型的实例。我已经为上面提到的 类 定义了 EncodeJson (argonaut) 编解码器。有什么办法可以使用 argonaut 将上述类型的列表转换为 json 吗?我的意思是,我可以实现以下目标吗?
List(new MissingParameter("name"), new InvalidParameter("email")).asJson
我没有用过argonaut,但我猜这里的问题是一个普遍的问题。这里列表的类型是List[Product with Serializable]
。而且编译器不知道如何将它序列化为 Json。
我建议你创建一个 Parameter 特征,用它扩展 类,并为 Parameter 编写一个序列化程序来检查所有具有模式匹配的类型:
注意:我使用的是play-json库,你应该适配argonaut。
import play.api.libs.json.{Writes, JsValue, Json}
import play.api.libs.json.Writes._
trait Parameter
case class MissingParameter (paramName: String) extends Parameter
case class InvalidParameter (paramName: String) extends Parameter
implicit val writes1 = Json.writes[MissingParameter]
implicit val writes2 = Json.writes[InvalidParameter]
implicit val implicitParamWrites = new Writes[Parameter] {
def writes(param: Parameter): JsValue = {
param match {
case missing: MissingParameter => Json.toJson(missing)
case invalid: InvalidParameter => Json.toJson(invalid)
}
}
}
val list: List[Parameter] = List( MissingParameter("p1"), InvalidParameter("i1") )
Json.toJson(list)
Feyyaz 的解决方案似乎是最佳解决方案。为了避免模式匹配每个可能的子类,我在父类中定义了一个契约,因此每个子类都必须提供一个编码器。不确定它是否完美,但它符合我的预期。我希望它能帮助正在寻找类似解决方案的人。
import argonaut._, Argonaut._
import scala.collection.TraversableLike
trait HasEncoder[T <: HasEncoder[T]] { self: T =>
def encoder: EncodeJson[T]
lazy val json = encoder.encode(self)
}
object HasEncoder {
implicit def listToRichHasEncoderList[A <: HasEncoder[_], Repr](coll: TraversableLike[A, Repr]): RichHasEncoderList[A, Repr] = new RichHasEncoderList[A, Repr](coll)
}
class RichHasEncoderList[A <: HasEncoder[_], Repr](coll: TraversableLike[A, Repr]) {
lazy val json = jArray(coll.toList.map(_.json))
}
在上面的解决方案中,argonaut 需要一个列表来构造一个 json 数组 (jArray),因此,我必须接受任何可遍历的内容并将其转换为列表。我不确定我是否可以改进那部分。
和一个测试用例(确保导入上述代码所在的包,以便隐式 'json' 值在列表中可用):
import argonaut._, Argonaut._
import org.scalatest.{Matchers, FlatSpec}
/**
* Created by jamesanto on 12/17/15.
*/
class HasEncoderTest extends FlatSpec with Matchers {
case class MissingParameter(name: String) extends HasEncoder[MissingParameter] {
override def encoder: EncodeJson[MissingParameter] = casecodec1(MissingParameter.apply, MissingParameter.unapply)("name")
}
case class InvalidParameter(name: String, expected: String, actual: String) extends HasEncoder[InvalidParameter] {
override def encoder: EncodeJson[InvalidParameter] = casecodec3(InvalidParameter.apply, InvalidParameter.unapply)("name", "expected", "actual")
}
it should "encode list of objects of classes that extend HasEncoder" in {
val list = List(MissingParameter("email"), InvalidParameter("dob", "DOB in yyyy/MM/dd format", "10/10/1985"))
list.json.nospaces should be ("""[{"name":"email"},{"name":"dob","expected":"DOB in yyyy/MM/dd format","actual":"10/10/1985"}]""")
}
}