2017-01-10 5 views
1

私はPlayフレームワークアプリケーション(Scala)でJsonリーダーを作成しようとしています。問題は、私のJsonの一部がちょっとファンキーで、値を取得するためにさらに処理する必要があるということです。たとえば、次の場合クラスとPlay Json - 複合オブジェクトの作成

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

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

基本的Field2ためtextnumフィールドは、テキストを分割することによって、値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機能的な構文の問題は、物事を計算するために前のフィールドを参照できないことです。適用を呼び出すと、生成されたReaderを変更できますが、計算したデータを追跡するためには中間型が必要です。さらに、特定の状況下では、機能的な構文でパフォーマンスの問題が発生する可能性があります。https://www.lucidchart.com/techblog/2016/08/29/speeding-up-restful-services-in-play-framework/ – gregghz

+0

@Jeff私は機能構文を使用する例を追加しました。 – gregghz

0

あなたはケースクラスが必要なのか、なぜ私は知らないが、あなたはまた、

(__ \ "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) 

であなたのニーズに合わせてJSONを変換することができ、その後、あなたはこれが美しく働くあなたのReads[Field1]

+0

ケースクラスは、すでに設計されたシステムの不可欠な部分です。また、私が提供したコードは、同じ行にある実際のコードの小さな近似ですが、はるかに大きいので、毎回jsonを変換することは実現可能ではないようです。 – Jeff

関連する問題