§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
Reads
sağ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.write
metodunu 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 (
and
combinator’ları tarafından yaratılan)FunctionalBuilder#CanBuildX
karmaşıkT
tipini alarak her birWrites
yoluna karşılık gelen tuple’a dönüştüren bir fonksiyon alır. BuReads
durumuna benzer olsa da bir case class’ınunapply
metoduOption
döndüğündenunlift
ile 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.