简体   繁体   English

在Scala中,如何将隐式转换应用于集合的子类型?

[英]In Scala, how to apply implicit conversion to subtypes of a collection?

I am trying to convert a list of objects to json using argonaut. 我试图使用argonaut将对象列表转换为json。 The list contains a list of validation errors of varying type. 该列表包含各种类型的验证错误的列表。 For example it can contain an instance of ' MissingParameter ' or, ' InvalidParameter ' or any other type. 例如,它可以包含“ MissingParameter ”或“ InvalidParameter ”或任何其他类型的实例。 I have defined EncodeJson (argonaut) codecs for both classes mentioned above. 我已经为上述两个类定义了EncodeJson(argonaut)编解码器。 Is there any way I can convert a list of the above types to json using argonaut? 有什么办法可以使用argonaut将上述类型的列表转换为json吗? I mean, can I achieve the following? 我的意思是,我可以实现以下目标吗?

List(new MissingParameter("name"), new InvalidParameter("email")).asJson

The solution by Feyyaz seems to be the best solution possible. Feyyaz的解决方案似乎是最好的解决方案。 Just to avoid pattern matching every possible subclass, I am defining a contract in the parent so every subclass must provide an encoder. 为了避免模式匹配每个可能的子类,我在父级中定义了一个契约,因此每个子类都必须提供一个编码器。 Not sure if it's perfect, but it does what I expect. 不知道它是否完美,但是可以达到我的期望。 I hope it helps someone who is looking for a similar solution. 我希望它对正在寻找类似解决方案的人有所帮助。

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))
} 

In the above solution, argonaut expects a list to construct a json array(jArray), so, I had to accept anything that's traversable and convert it to a list. 在上述解决方案中,argonaut期望一个列表构造一个json数组(jArray),因此,我必须接受所有可遍历的东西并将其转换为列表。 I am not sure if I can improve that part. 我不确定是否可以改善这一部分。

And a test case(Make sure to import the package where the above code resides so that the implicit 'json' value will be available on the list): 和一个测试用例(确保导入上面代码所在的包,以便隐式“ 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"}]""")
  }
}

I haven't used argonaut, but I guess the problem here is a general issue. 我没有用过argonaut,但是我想这里的问题是一个普遍的问题。 The type of the list here is List[Product with Serializable] . 此处List[Product with Serializable]的类型为List[Product with Serializable] And the compiler won't know how to serialize it to Json. 而且编译器不知道如何将其序列化为Json。

I would suggest you to create a Parameter trait, extend the classes with it, and write a serializer for Parameter that checks all the types with pattern matching: 我建议您创建一个Parameter trait,使用它扩展类,并为Parameter编写一个序列化程序,该序列化程序检查所有具有模式匹配的类型:

Note: I'm using play-json library, you should adapt it to argonaut. 注意:我正在使用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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM