2017-01-10 107 views
1

我想在我的Play框架应用程序(Scala)中创建Json读者。问题是,我的Json的一部分是有点时髦,并需要进一步处理来检索值。例如:玩Json - 复杂的对象创建

{ 
    "field1":"value1", 
    "field2":"value/1", 
    "num2":2 
} 

与case类:

case class Field1(text: String, fields: Field2) 
case class Field2(text: String, num: Int, num2: Int) 

基本上为Field2textnum字段从值value/1衍生,通过分割文本。这是分离器功能:

def splitter(path: String, num2: Int): Field2 = { 
    val split = path.split("\\") 
    Field2(split(0), split(1).toInt, num2) 
} 

这是相当简单的,实际的分配器功能要复杂得多。基本上构造这个对象Field2的唯一方法是将一个单一的字符串传递给一个能够吐出所需对象的函数。

如何去创造Field2(通过扩展为Field1)读者?

这是我到目前为止有:

object Field1 { 
    implicit val reader = (
     (__ \ "field1").read[String] and 
     (__).read[Field2] 
    ) (Field1.apply _) 
} 

object Field2 { 
    implicit val reader = (
     splitter((__ \ "field2").read[String], (__ \ "num2")) 
    ) // Obviously incorrect syntax + type mismatch, but this is roughly what I'm trying to accomplish. 
} 

回答

0

如果使用非功能性的组合子语法就显得有点简单,我认为:

object Field2 { 
    implicit val reader = Reads[Field2] { json => 
    for { 
     path <- (json \ "field2").validate[String] 
     num2 <- (json \ "num2").validate[Int] 
    } yield splitter(path, num2) 
    } 
} 

此外,如果你想分路器,进一步验证输入有它返回JsResult[Field2]像这样:

def splitter(path: String, num2: Int): JsResult[Field2] = { 
    val split = path.split("\\") 
    if (split.size != 2) { 
    JsError(s"$path must be of the form: {field}\\{num}") 
    } else { 
    Try(Field2(split(0), split(1).toInt, num2)).map(JsSuccess(_)).getOrElse { 
     JsError(s"${split(1)} is not a valid Int") 
    } 
    } 
} 

,然后修改读者:

object Field2 { 
    implicit val reader = Reads[Field2] { json => 
    for { 
     path <- (json \ "field2").validate[String] 
     num2 <- (json \ "num2").validate[Int] 
     field2 <- splitter(path, num2) 
    } yield field2 
    } 
} 

如果你仍然喜欢使用功能的语法和你不介意缺乏验证该分离器不试试这个的:

def splitter(path: String, num2: Int): Field2 = { 
    val split = path.split("\\") 
    Field2(split(0), split(1).toInt, num2) 
} 

implicit val reader = (
    (__ \ "field2").read[String] and 
    (__ \ "num2").read[Int] 
)(splitter _) 

我建议反对这是不安全的(split(1)toInt都可能会引发异常),并且函数语法可能会有性能问题。请参阅https://www.lucidchart.com/techblog/2016/08/29/speeding-up-restful-services-in-play-framework/

+0

这精美的作品。但是没有办法使用常规的函数式语法吗?我在我的代码的其他部分中使用过,我想保持一致。 – Jeff

+0

@Jeff函数语法的问题在于,您无法引用先前的字段来计算事物。一旦你调用apply,你可以修改生成的Reader,但是你需要一个中间类型来跟踪你计算的数据。另外,在某些情况下,您可能会遇到功能语法的性能问题:https://www.lucidchart.com/techblog/2016/08/29/speeding-up-restful-services-in-play-framework/ – gregghz

+0

@Jeff我添加了一个使用函数语法的例子。 – gregghz

0

我不知道为什么你需要case类,但你也可以将JSON满足您的需求与

(__ \ "field2" \ "num2").json.copyFrom((__ \ "num2").json.pick) and 
    (__ \ "field2").json.update(
     of[String].map { o => 
     val split = o.split("/") 
     Json.obj(
      "text" -> split(0), 
      "num" -> split(1).toInt 
     ) 
     } 
    ) 
).reduce andThen (__ \ "num2").json.prune 

scala> j: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"field2":{"num2":2,"text":"value","num":1},"field1":"value1"},/num2) 

,然后你可以使用你Reads[Field1]

+0

案例类是已经设计的系统的组成部分。另外,我提供的代码是实际代码的一个小近似值,它们沿着相同的线条,但是要大得多,所以每次转换json似乎都不可行。 – Jeff