如何摆脱这种隐式转换?

How to get rid of this implicit conversion?

假设我正在使用 json4s 来解析 JSON:

val str = """{"a":"aaaa", "x": 0}"""
val json = JsonMethods.parse(str)
val a = for(JObject(fields) <- json; JField("a", JString(a)) <- fields) yield a

a的类型是List[String],但我需要Option[String],所以我调用headOption:

val a = (
  for(JObject(fields) <- json; JField("a", JString(a)) <- fields) yield a
).headOption

因为我发现自己一次又一次地调用 headOption,所以我尝试了 隐式转换:

object X { implicit def foo[A](as: List[A]): Option[A] = as.headOption }
import X.foo
val a: Option[String] = 
  for(JObject(fields) <- json; JField("a", JString(a)) <- fields) yield a

隐式转换有效,但我不喜欢它。你有什么建议?

一种方法是使用 json4 的类型类,例如json4s-scalaz 有那些:

trait JSONR[A] {
  def read(json: JValue): Result[A]
}

trait JSONW[A] {
  def write(value: A): JValue
}

source

为了语法简单,可以为 JValue:

定义扩展方法
implicit class JValueOps(value: JValue) {
  def validate[A: JSONR]: ValidationNel[Error, A] = implicitly[JSONR[A]].read(value)
  def read[A: JSONR]: Error \/ A = implicitly[JSONR[A]].read(value).disjunction.leftMap(_.head)
}

然后进行遍历,并解析遍历的结果JValue,如下所示:

val str =
  """
    |{
    |  "a": "aaaa",
    |  "x": 0
    |}""".stripMargin

val json = parseJson(str)

(json \ "a").read[Option[String]]
// \/-(Some(aaaa))

(json \ "b").read[Option[String]]
// \/-(None)

(json \ "a").validate[Option[String]]
// Success(Some(aaaa))

(json \ "b").validate[Option[String]]
// Success(None)

可以像这样定义自己的 JSONR[A]/JSONW[A] 实例(并将它们放在隐式范围内):

case class MyA(a: Option[String], x: Int)

implicit val myARead: JSONR[MyA] = JSON.readE[MyA] { json =>
  for {
    a <- (json \ "a").read[Option[String]]
    x <- (json \ "x").read[Int]
  } yield MyA(a, x)
}

implicit val myAWrite: JSONW[MyA] = JSON.write[MyA] { myA =>
  ("a" -> myA.a) ~
    ("x" -> myA.x)
}


json.read[MyA]
// \/-(MyA(Some(aaaa),0))

json.validate[MyA]
// Success(MyA(Some(aaaa),0))

MyA(Some("aaaa"), 0).toJson
// JObject(List((a,JString(aaaa)), (x,JInt(0))))

请注意,read[A]write[a] 方法是胶水代码,json4s-scalaz 中尚不可用,您可以找到来源 here. There are also more examples

然后json.read[A]returns一个Error \/ A and json.validate[A] yields a Validation types from scalaz. There are similar types in cats.

myARead 是 monadic 解析样式的示例(通过 flatMap 组合)。另一种方法是使用应用解析。这样做的好处是所有验证错误都会累积:

val myARead2: JSONR[MyA] = JSON.read[MyA] { json =>
  (
    (json \ "a").validate[Option[String]] |@|
    (json \ "x").validate[Int]
  ).tupled.map(MyA.tupled)
}


val myARead3: JSONR[MyA] = JSON.read[MyA] {
  for {
    a <- field[Option[String]]("a") _
    x <- field[Int]("x") _
  } yield (a |@| x).tupled.map(MyA.tupled)
}

还有https://github.com/json4s/json4s/blob/3.4/core/src/main/scala/org/json4s/JsonFormat.scala