如何使用Play框架从组合的复合类组创建JSON输出

时间:2017-02-07 21:17:57

标签: json scala playframework

我是Scala新手,扩展别人的代码。该代码使用Play框架的JSON库。我正在访问类Future[Option[A]]Future[Option[List[B]]的对象。类AB每个都有自己的JSON writes方法,因此每个方法都可以返回JSON作为对Web请求的响应。我正在尝试将这些组合成一个JSON响应,我可以将其作为HTTP响应返回。

我认为创建一个将AB组成一个类的类可以让我这样做,就像这样:

case class AAndB(a: Future[Option[A]], b: Future[Option[List[B]]])
object AAndB {
    implicit val implicitAAndBWrites = Json.writes[AAndB]
}

但是到处都失败了。 A和B的结构都是这样的:

sealed trait A extends SuperClass {
    val a1: String = "identifier"
}

case class SubA(a2: ClassA2) extends A {
    override val a1: String = "sub identifier"
}

object SubA {
    val writes = Writes[SubA] { aa =>
        Json.obj(
            "a1" -> aa.a1
            "a2" -> aa.a2
        )
    }
}

由于B作为List访问,因此预期的输出将沿着这些行:

{  
   "a":{  
      "a1":"val1",
      "a2":"val2"
   },
   "b":[  
      {  
         "b1":"val 3",
         "b2":"val 4"
      },
      {  
         "b1":"val 5",
         "b2":"val 6"
      },
      {  
         "b1":"val 7",
         "b2":"val 8"
      }
   ]
}

感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

正如@cchantep在你的问题的评论中提到的那样,将Future作为case class声明的一部分是非常不寻常的 - 案例类非常适合封装不可变域对象(即不随着时间的推移而变化)但是一旦涉及Future[T],您可能会有多种结果:

  • Future尚未完成
  • Future失败
  • Future已成功完成,并包含T个实例

您不希望将此时间内容与转换为JSON的行为纠缠在一起。因此,您应该在删除Future的情况下对包装类进行建模:

case class AAndB(a: Option[A], b: Option[List[B]])
object AAndB {
  implicit val implicitAAndBWrites = Json.writes[AAndB]
}

而是使用Scala / Play在Controller类中对它们进行非常简洁的处理来访问每个内容。在下面的示例中,假设存在注入的服务类,如下所示:

class AService {
  def findA(id:Int):Future[Option[A]] = ...
}

class BListService {
  def findBs(id:Int):Option[Future[List[B]]] = ...
}

以下是我们的控制器方法的样子:

def showCombinedJson(id:Int) = Action.async {

  val fMaybeA = aService.findA(id)
  val fMaybeBs = bService.findBs(id)

  for {
    maybeA <- fMaybeA
    maybeBs <- fMaybeBs
  } yield {
    Ok(Json.toJson(AAndB(maybeA, maybeBs)))
  }
}

所以在这里我们并行启动A和B查询(我们必须在之外 for - 理解来实现这种并行性)。如果{当yield s成功完成时,for - 理解的Future块将仅执行 - 此时可以安全地访问内容。然后,构建包装类的实例,转换为JSON并将Ok结果返回到Play是一个简单的问题。

请注意,yield的结果本身位于Future内(在这种情况下它是Future[Result])所以我们使用Play的{{ 1}}动作构建器来处理这个问题 - 让Play处理所有实际的等待事件。