§JSON Reads/Writes/Format Combinator’lar
JSON temelleri sayfasında JsValue yapıları ile diğer veri türleri arasında dönüşüm yapmak için kulanılan Reads ve Writes dönüştürücüleri tanıtmıştık. Bu sayfada ise bu dönüştürücülerin nasıl oluşturulacağını ve dönüşüm esnasında nasıl doğrulama yapılacağını çok daha ayrıntılı anlatacağız.
Bu sayfadaki örnekler aşağıdaki JsValue yapısını ve ona karşılık gelen modeli kullanacak:
import play.api.libs.json._
val json: JsValue = Json.parse("""
{
"name" : "Watership Down",
"location" : {
"lat" : 51.235685,
"long" : -1.309197
},
"residents" : [ {
"name" : "Fiver",
"age" : 4,
"role" : null
}, {
"name" : "Bigwig",
"age" : 6,
"role" : "Owsla"
} ]
}
""")
case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])§JsPath
JsPath, Reads/Writes oluşturmak için temel yapı taşıdır. JsPath verinin bir JsValue yapısı içindeki konumunu ifade eder. JsPath nesnesini (kök yol) kullanarak JsValue içinde gezinme sözdizimine benzer şekilde bir çocuk JsPath örneği tanımlayabilirsiniz:
import play.api.libs.json._
val json = { ... }
// Simple path
val latPath = JsPath \ "location" \ "lat"
// Recursive path
val namesPath = JsPath \\ "name"
// Indexed path
val firstResidentPath = (JsPath \ "residents")(0)
play.api.libs.json paketi JsPath için bir öteki ad tanımlar: __ (çift altçizgi). Eğer isterseniz bunu şu şekilde kullanabilirsiniz:
val longPath = __ \ "location" \ "long"§Reads
Reads dönüştürücüler bir JsValue’dan başka bir türe dönüşüm yapmak için kullanılırlar. Daha karmaşık Reads oluşturmak için birden fazla Reads bir araya getirebilirsiniz.
Reads oluşturabilmek için aşağıdaki import’lara ihtiyacınız olacak:
import play.api.libs.json._ // JSON library
import play.api.libs.json.Reads._ // Custom validation helpers
import play.api.libs.functional.syntax._ // Combinator syntax§Yol Reads
JsPath bir JsValue’ya belirtilen yolda başka bir Reads uygulayan özel bir Reads oluşturmak için metotlara sahiptir:
JsPath.read[T](implicit r: Reads[T]): Reads[T]-JsValue’ya belirtilen(bu) yolda örtük argümanr’yi uygulayan birReads[T]yaratır.JsPath.readNullable[T](implicit r: Reads[T]): Reads[Option[T]]- Bulunmayan ya da değeri null olan yollar için kullanın.
Not: JSON kütüphanesi String, Int, Double, vb. temel türler için örtük
Readssağlar.
Tek bir yol Reads tanımı şöyledir:
val nameReads: Reads[String] = (JsPath \ "name").read[String]§Karmaşık Reads
Bağımsız yol Reads tanımlarını bir araya getirerek karmaşık modelleri dönüştürmek için daha karmaşık Reads oluşturabilirsiniz.
Daha anlaşılır olması için birleştirme özelliğini iki parçaya ayıracağız. Önce Reads nesnelerini and combinator kullanarak birleştirin:
val locationReadsBuilder =
(JsPath \ "lat").read[Double] and
(JsPath \ "long").read[Double]
Bu FunctionalBuilder[Reads]#CanBuild2[Double, Double] şeklinde bir tür oluşturacaktır. Bu ara nesne hakkında çok fazla düşünmenize gerek yoktur. Karmaşık Reads oluşturmak için kullanıldığını bilmeniz yeterli.
Daha sonra CanBuildX‘in apply metodunu değerleri modelinize dönüştürecek bir fonksiyon ile çağırın. Bu bir karmaşık Reads döndürecektir. Eğer bir case class’ınız varsa doğrudan onun apply metodunu kullanabilirsiniz.
implicit val locationReads = locationReadsBuilder.apply(Location.apply _)
Aynı kod aşağıda tek bir ifade olarak verilmiştir:
implicit val locationReads: Reads[Location] = (
(JsPath \ "lat").read[Double] and
(JsPath \ "long").read[Double]
)(Location.apply _)§Reads ile doğrulama
JSON temelleri sayfasında JsValue.validate metodunun bir JsValue’dan başka bir türe dönüşüm ve doğrulama için tercih edilen yol olduğundan bahsetmiştik. Temel desen aşağıda yer alıyor:
val json = { ... }
val nameReads: Reads[String] = (JsPath \ "name").read[String]
val nameResult: JsResult[String] = json.validate[String](nameReads)
nameResult match {
case s: JsSuccess[String] => println("Name: " + s.get)
case e: JsError => println("Errors: " + JsError.toFlatJson(e).toString())
}
Reads için varsayılan doğrulama tür dönüşüm hataları gibi çok basit doğrulamalardan ibarettir. Fakat Reads doğrulama yardımcılarını kullanarak özel doğrulama kuralları tanımlayabilirsiniz. Çok kullanılanlardan bazıları şöyle:
Reads.email- Bir String’in email biçiminde olduğunu doğrular.Reads.minLength(nb)- Bir String’in en az uzunluğunu doğrular.Reads.min- Bir sayının en az değerini doğrular.Reads.max- Bir sayının en çok değerini doğrular.Reads[A] keepAnd Reads[B] => Reads[A]-Reads[A]veReads[B]’yi deneyen fakat yalnızcaReads[A]’nın sonucunu saklayan operatör (Scala parser combinator’ları bilenler içinkeepAnd == <~).Reads[A] andKeep Reads[B] => Reads[B]-Reads[A]veReads[B]’yi deneyen fakat yalnızcaReads[B]’nın sonucunu saklayan operatör (Scala parser combinator’ları bilenler içinandKeep == ~>).Reads[A] or Reads[B] => Reads- Mantıksal OR işlemi gerçekleştiren ve başarılı olan son Reads sonucunu saklayan operatör.
Doğrulama eklemek için yardımcı metotları JsPath.read metoduna argümanlar olarak uygulayın:
val improvedNameReads =
(JsPath \ "name").read[String](minLength[String](2))§Hepsini bir araya getirelim
Karmaşık Reads ve özel doğrulama kullanarak örnek modelimiz için etkin bir Reads seti tanımlayabilir ve uygulayabiliriz:
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
implicit val locationReads: Reads[Location] = (
(JsPath \ "lat").read[Double](min(-90.0) keepAnd max(90.0)) and
(JsPath \ "long").read[Double](min(-180.0) keepAnd max(180.0))
)(Location.apply _)
implicit val residentReads: Reads[Resident] = (
(JsPath \ "name").read[String](minLength[String](2)) and
(JsPath \ "age").read[Int](min(0) keepAnd max(150)) and
(JsPath \ "role").readNullable[String]
)(Resident.apply _)
implicit val placeReads: Reads[Place] = (
(JsPath \ "name").read[String](minLength[String](2)) and
(JsPath \ "location").read[Location] and
(JsPath \ "residents").read[Seq[Resident]]
)(Place.apply _)
val json = { ... }
json.validate[Place] match {
case s: JsSuccess[Place] => {
val place: Place = s.get
// do something with place
}
case e: JsError => {
// error handling flow
}
}
Karmaşık Reads nesneleri iç içe geçebilir. Bu örnekte placeReads belirli yollarda daha önceden tanımlanmış örtük locationReads ve residentReads tanımlarını kullanır.
§Writes
Writes dönüştürücüler bir türden JsValue’ya dönüşüm yapmak için kullanılırlar.
Reads’e benzer şekilde JsPath ve combinator’lar kullanarak karmaşık Writes oluşturabilirsiniz. Örneğimiz için Writes aşağıdaki gibidir:
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val locationWrites: Writes[Location] = (
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
)(unlift(Location.unapply))
implicit val residentWrites: Writes[Resident] = (
(JsPath \ "name").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "role").writeNullable[String]
)(unlift(Resident.unapply))
implicit val placeWrites: Writes[Place] = (
(JsPath \ "name").write[String] and
(JsPath \ "location").write[Location] and
(JsPath \ "residents").write[Seq[Resident]]
)(unlift(Place.unapply))
val place = Place(
"Watership Down",
Location(51.235685, -1.309197),
Seq(
Resident("Fiver", 4, None),
Resident("Bigwig", 6, Some("Owsla"))
)
)
val json = Json.toJson(place)
Writes ve Reads arasında bazı farklılıklar vardır:
- Bağımsız yol
Writes’larJsPath.writemetodunu kullanarak oluşturulurlar. JsValue’ya dönüşüm esnasında bir doğrulama yoktur. Bu yapınızı daha basit tutar ve doğrulama yardımcılarına ihtiyacınız yoktur.- Ara tür (
andcombinator’ları tarafından yaratılan)FunctionalBuilder#CanBuildXkarmaşıkTtipini alarak her birWritesyoluna karşılık gelen tuple’a dönüştüren bir fonksiyon alır. BuReadsdurumuna benzer olsa da bir case class’ınunapplymetoduOptiondöndüğündenunliftile birlikte kullanılmalıdır.
§Özyinelemeli Türler
Örneğimizin ele almadığı özel bir durum özyinelemeli türler için Reads ve Writes kullanımının nasıl olacağıdır. JsPath call-by-name parametreler alan lazyRead ve lazyWrite metodları sunar:
case class User(name: String, friends: Seq[User])
implicit lazy val userReads: Reads[User] = (
(__ \ "name").read[String] and
(__ \ "friends").lazyRead(Reads.seq[User](userReads))
)(User)
implicit lazy val userWrites: Writes[User] = (
(__ \ "name").write[String] and
(__ \ "friends").lazyWrite(Writes.seq[User](userWrites))
)(unlift(User.unapply))§Format
Format[T] yalnızca Reads ve Writes trait’lerinin karışımıdır ve bileşenlerinin yerine örtük dönüştürme için kullanılabilir.
§Reads ve Writes ile Format yaratmak
Aynı türün Reads ve Writes’larını kullanarak bir Format tanımlayabilirsiniz:
val locationReads: Reads[Location] = (
(JsPath \ "lat").read[Double](min(-90.0) keepAnd max(90.0)) and
(JsPath \ "long").read[Double](min(-180.0) keepAnd max(180.0))
)(Location.apply _)
val locationWrites: Writes[Location] = (
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
)(unlift(Location.unapply))
implicit val locationFormat: Format[Location] =
Format(locationReads, locationWrites)§Combinator’lar kullanarak Format yaratmak
Eğer Reads ve Writes’ınız simetrik ise (gerçek uygulamalarda sık rastlanmayabilir) combinator’lar aracılığıyla doğrudan bir Format tanımlayabilirsiniz:
implicit val locationFormat: Format[Location] = (
(JsPath \ "lat").format[Double](min(-90.0) keepAnd max(90.0)) and
(JsPath \ "long").format[Double](min(-180.0) keepAnd max(180.0))
)(Location.apply, unlift(Location.unapply))
Sonraki: JSON Transformer'lar
Dokümantasyonun bu çevirisi Play ekibi tarafından yapılmamaktadır. Eğer bir hata bulduysanız, bu sayfanın kaynak kodu burada bulunmaktadır. Dokümantasyon yönergelerini okuduktan sonra lütfen katkı yapmaktan çekinmeyin.