Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/json/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/image-processing/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Json 如何在编译时以编程方式创建验证契约?_Json_Scala_Validation_Json Deserialization_Play Json - Fatal编程技术网

Json 如何在编译时以编程方式创建验证契约?

Json 如何在编译时以编程方式创建验证契约?,json,scala,validation,json-deserialization,play-json,Json,Scala,Validation,Json Deserialization,Play Json,如果这是XY问题,我提前道歉 tl;博士: 我希望有一个类型为[Request.type,Response.type]的编译时映射,这样我就可以有效地说,如果我发送消息Request,CLI在编译时应该知道如何反序列化其预期的响应,而不管它在运行时之前不知道发送了什么类型的请求 太长;仍然是: 我有一个与HTTP服务器通信的CLI,根据发送到HTTP服务器的消息类型,我想根据一个案例验证JSON响应 例如,如果我向HTTP服务器发送一条AddFoo消息,我可能希望验证JSON响应是否可以反序列化

如果这是XY问题,我提前道歉

tl;博士: 我希望有一个类型为
[Request.type,Response.type]
的编译时映射,这样我就可以有效地说,如果我发送消息
Request
,CLI在编译时应该知道如何反序列化其预期的
响应
,而不管它在运行时之前不知道发送了什么类型的请求

太长;仍然是: 我有一个与HTTP服务器通信的CLI,根据发送到HTTP服务器的消息类型,我想根据一个案例验证JSON响应

例如,如果我向HTTP服务器发送一条
AddFoo
消息,我可能希望验证JSON响应是否可以反序列化为
adddfoo
,等等

我目前的解决方案相当粗糙。使用play json,我试图使用从
config.mode
(即,向CLI发出的命令)到预期响应的隐式
读取的映射来解析json响应

我的代码如下所示:

val modeToResponseReads: Map[String, Reads[_]] = Map(
  Modes.ADD_FOO -> AddedFoo.addedFooReads,
  Modes.ADD_BOO -> AddedBoo.addedBooReads,
  Modes.GET_WOO -> GetWooResponse.getWooReads,
)

parser.parse(args, MyConfig()) match {

  case Some(config) => try {
    val exec = new MyHttpExecutor(remoteUri, config)
    val res = Await.result(exec.getResponse, 100.seconds)

    // passing `Reads` to `as` because JsValue#as[T] cannot be
    // applied at runtime -- only compile-time.
    val _ = Json.parse(res.json.toString)
                .as(modeToResponseReads(config.mode))
    
    exec.actorSystem.terminate()
    exec.wsClient.close()
  } catch {
    case t: Throwable => logger.error(t.getMessage)
  }

  case None => {
    logger.error("Bad arguments.")
    sys.exit(1)
  }
}
abstract class Response
abstract class Request[+A <: Response]

case class Foo(id: String)

object Foo {
  implicit val fooReads = Json.reads[Foo]
  implicit val fooFormat = Json.format[Foo]
}

case class FooResponse(foo: Foo) extends Response {
  def greet = println("woo hoo!")
}

object FooResponse {
  implicit val fooRespReads = Json.reads[FooResponse]
  implicit val fooRespFormat = Json.format[FooResponse]
}

case class FooRequest() extends Request[FooResponse] {
  type ResponseType = FooResponse
}

object Main extends App {
  val req: FooRequest = new FooRequest()
  val foo = Foo("12345")
  val resp = new FooResponse(foo)

  val respJsonString = Json.toJson(resp).toString
  println(Json.parse(respJsonString).as[req.ResponseType])
}
虽然这样做有效,但随着消息数量的增加,这是一个令人难以置信的混乱局面,变得越来越无法维护。此外,我发现这种模式需要在任何需要进行某种验证或转换的地方复制(例如,
Future[Any]
转换为
Future[AddedFoo]


我的方法肯定不是正确的。。。传统上是如何做到这一点的?如果这是正确的方法(请不要),是否可以进行优化?

我通过将契约直接编码到子
请求
类中来实现这一点。也就是说,子
Request
类将持有
ResponseType
类型,基类强制执行协变类型

所以我可以这样做:

val modeToResponseReads: Map[String, Reads[_]] = Map(
  Modes.ADD_FOO -> AddedFoo.addedFooReads,
  Modes.ADD_BOO -> AddedBoo.addedBooReads,
  Modes.GET_WOO -> GetWooResponse.getWooReads,
)

parser.parse(args, MyConfig()) match {

  case Some(config) => try {
    val exec = new MyHttpExecutor(remoteUri, config)
    val res = Await.result(exec.getResponse, 100.seconds)

    // passing `Reads` to `as` because JsValue#as[T] cannot be
    // applied at runtime -- only compile-time.
    val _ = Json.parse(res.json.toString)
                .as(modeToResponseReads(config.mode))
    
    exec.actorSystem.terminate()
    exec.wsClient.close()
  } catch {
    case t: Throwable => logger.error(t.getMessage)
  }

  case None => {
    logger.error("Bad arguments.")
    sys.exit(1)
  }
}
abstract class Response
abstract class Request[+A <: Response]

case class Foo(id: String)

object Foo {
  implicit val fooReads = Json.reads[Foo]
  implicit val fooFormat = Json.format[Foo]
}

case class FooResponse(foo: Foo) extends Response {
  def greet = println("woo hoo!")
}

object FooResponse {
  implicit val fooRespReads = Json.reads[FooResponse]
  implicit val fooRespFormat = Json.format[FooResponse]
}

case class FooRequest() extends Request[FooResponse] {
  type ResponseType = FooResponse
}

object Main extends App {
  val req: FooRequest = new FooRequest()
  val foo = Foo("12345")
  val resp = new FooResponse(foo)

  val respJsonString = Json.toJson(resp).toString
  println(Json.parse(respJsonString).as[req.ResponseType])
}
抽象类响应

抽象类请求[+A你的意思是它是在运行时传递的,也就是说当HTTP请求发出时?我只会在类型都扩展某个密封的特征或抽象类的情况下尝试这样做,因为只有类匹配才能帮助你在那之后对它们进行排序。@MichaelZajac all requests extend
MyBaseRequest
,所有响应都扩展
MyBaseResponse
。我发现这是一个相当顽皮的问题……我认为这是一个众所周知的模式。:)我遇到过几次类似的情况,必须通过创建一个与类型匹配的
Reads[MyBaseResponse]
来解决它。如果您更改
Reads[\uu],您可能至少能够返回一个
MyBaseResponse
读取[_