有没有办法使用反射来提取字段并在unapply函数中构建元组?

时间:2018-01-23 17:23:19

标签: json scala playframework

我正在尝试为json建模消息传递。 这是json的例子:

// message1
{ 
  "messageType" : "JoinChannelCommand",
  "content" : {
    "channelId" : "abc"
  }
} 

// message 2
{
  "messageType" : "SyncCommand",
  "content" : {
     "updatedAfter" : 1516726583
  }
}

下面是代表json

的类
case class JoinChannelCommand(channelId: String)
object JoinChannelCommand {
    def unapply(js: JsValue) = {
      (js \ "messageType").asOpt[String].flatMap { messageType =>
        val className = this.getClass.getSimpleName.stripSuffix("$")

    if (messageType == className) {
      (js \ "content" \ "channelId").asOpt[String]
    } else {
      None
    }
  }
}

case class SyncCommand(updateAfter: Timestamp)
object SyncCommand {
    def unapply(js: JsValue) = {
      (js \ "messageType").asOpt[String].flatMap { messageType =>
        val className = this.getClass.getSimpleName.stripSuffix("$")

        if (messageType == className) {
          (js \ "content" \ "updatedAfter").asOpt[Long].map(new Timestamp(_))

        } else {
          None
        }
      }
    }
  }

这样,我就可以用这种方式进行模式匹配

x matchs {
  case JoinChannelCommand(channelId) =>
  case SyncCommand(updatedAfter) =>
}

如您所见,有很多样板代码。 我想知道是否有办法在运行时检查字段(可能像反射),然后构建一个在unapply函数中使用的元组。

然后我只需要写:

trait MagicCommand {
  def unapply(js: JsValue) = { // <- where magic happened
  }
}
case class JoinChannelCommand(channelId: String)
object JoinChannelCommand extends MagicCommand

当命令量变大时会更加方便。

1 个答案:

答案 0 :(得分:0)

使用uPickle,请参阅CustomKeys部分中的最后一个示例here。基本上,它在编码时在JSON对象中添加$type字段,并使用该字段映射到适当的案例类。

或者,如果您将命令分布在特定的REST请求处理程序上(并且可选地消除messageTypecontent封装),使用play json validate宏的验证会变得更容易。

例如

;

case class JoinChannelCommand(channelId: String)
object JoinChannelCommand {
     implicit lazy val joinChannelCommandFormat = Json.format[JoinChannelFormat]
}

假设您正在POST到以下控制器方法

def joinChannelHandler = Action.async(parse.default) { implicit request =>
      val joinChannelCommandOpt: Option[JoinChannelCommand] = request.body.asJson.get.validate[JoinChannelCommand].asOpt
      joinChannelCommandOpt match {
           case None => ....
           case Some(c: JoinChannelCommand) => ....
      }
      ...
}  

它看起来很精简。全自动。如果您的案例类包含Option字段,则只要可选字段值为None,结果编码的json就不会具有这些字段。这与JS处理JSON的方式非常相似。

希望这有帮助。