有没有办法使用默认的 "built-in"(宏生成的)并且只覆盖单个 属性?

Is there a way to use the default "built-in" (macro-generated) and only override the single property?

我正在尝试将字段 vpid 作为字符串读取,而不必编写整个读取函数。

旧代码有效,除非 vpid 是整数。

case class SectionProduct(vpid: Option[String], name: Option[String], quantity: Option[Int]) {}

object SectionProduct {
  implicit val config: Aux[WithDefaultValues] = JsonConfiguration[Json.WithDefaultValues](SnakeCase)
  implicit val format: OFormat[SectionProduct] = Json.format[SectionProduct]
  implicit val writes: OWrites[SectionProduct] = Json.writes[SectionProduct]
  // implicit val reads:  Reads[SectionProduct] =   Json.reads[SectionProduct]
}

新代码,如果可能的话,我试图避免写出整个 reads2 块。

val intToString: Reads[String] = implicitly[Reads[Int]].map(x => x.toString)

implicit val reads2: Reads[SectionProduct] = (
    ((JsPath \ "vpid").readNullable[String] or (JsPath \ "vpid").readNullable[String](intToString)) and
      (JsPath \ "name").readNullable[String] and
      (JsPath \ "quantity").readNullable[Int]
    ) (SectionProduct.apply _)

您可以预处理您的 JSON 以使其符合生成的宏的预期 Reads:

implicit val reads: Reads[SectionProduct] = Json.reads[SectionProduct].preprocess {
    case obj: JsObject =>
      obj.as(
        Reads
          .at[Int](__ \ "vpid")
          .map { vpid => obj + ("vpid" -> JsString(vpid.toString)) }
          .orElse(Reads.pure(obj))
      )
  }

Play-JSON 没有为基于宏的编解码器提供每个字段的配置,并且 vpid 没有定义特定类型(还有其他可选的 String 在同一个 SectionProduct).

对于特定的类型,它可以很容易地实现(并且除了改进类型的特定用法之外,还有其他好处)。

import play.api.libs.json._

final class Vpid(val value: String) extends AnyVal

object Vpid {
  import scala.language.implicitConversions

  @deprecated("Use .value explicitly", "")
  /*
   * {{{
   * val compatStr: String = new Vpid("foo")
   * // => compatStr: String = foo
   * }}}
   */
  implicit def toString(id: Vpid): String = id.value

  implicit def reads: Reads[Vpid] = Json.valueReads[Vpid].orElse(
    implicitly[Reads[Int]].map { i => new Vpid(i.toString) })
}

case class SectionProduct(
  vpid: Option[Vpid],
  name: Option[String],
  quantity: Option[Int])

implicit val reads: Reads[SectionProduct] = Json.reads

在这种情况下:

val input1 = Json.parse("""{
  "vpid": "foo",
  "name": "bar"
}""")

input1.validate[SectionProduct]
// => JsSuccess(SectionProduct(Some(Vpid@18cc6),Some(bar),None),)

val input2 = Json.parse("""{
  "vpid": 1,
  "name": "bar"
}""")

input2.validate[SectionProduct]
// => JsSuccess(SectionProduct(Some(Vpid@31),Some(bar),None),)