定义 Reads[X],其中 X 具有私有构造函数

Defining Reads[X] where X has Private Constructor

给定 Person class 和 'smart constructor',即 Person#build 中只有 "valid" 个值将构造 Person:

case class Person private(age: Int)

object Person {
  def build(age: Int): Option[Person] = 
    if (age >= 0 && age <= 125) Some(Person(age)) else None     
}

我通过将它放入 Person 的伴生对象中创建了一个 Reads[Person]

import play.api.libs.json._
import play.api.libs.functional.syntax._

  implicit val reads: Reads[Person] = 
   (__ \ 'age).read[Int].map(a => 
    Person.build(a).getOrElse(throw new RuntimeException("invalid age")            
   ) 

有没有更好的方法?如果可能的话,我宁愿避免这个例外。

如果您愿意放弃游戏功能风格,您可以利用 JsResult monad:

new Format[Person] {
  override def writes(o: Person): JsValue = ???

  override def reads(json: JsValue): JsResult[Person] = {
    try{
      json.as[JsObject].value.get("age")
        .flatMap(age => Person.build(age.as[Int])).map(JsSuccess(_))
        .getOrElse(JsError("wrong age"))
    } catch {
      case e: Exception =>
        JsError("wrong age")
    }
  }
}

val person = Json.toJson(Person.build(30))

val parsed: \/[String, Person] =  person.validate[Person].map(\/-(_)).getOrElse(-\/("some bad request"))

所以它比我在评论中建议的要复杂一些,但基本上你可以将解析包装到一个方法中,该方法将 JsSuccess 转换为右侧,将 JsError 转换为左侧, try/catch 我用来捕获发送的 json 不是一个对象而只是一个值或者年龄字段不是整数的情况。