Playframework(9)Scala Project and Working with JSON
Playframework(9)ScalaProjectandWorkingwithJSON
5.WorkingwithJSON
5.1ThePlayJSONlibrary
WerecommendtodealwithJSONbasedthetypeclasslibraryplay.api.libs.json.
Thislibraryisbuiltontopofhttps://github.com/codahale/jerkson/,whichisaScalawrapperaroundthesuper-fastjavabasedJSONlibrary,Jackson.
TherearesomeJSONdatatypes:
JsObject
JsNull
JsUndefined
JsBoolean
JsNumber
JsArray
JsString
AlloftheminheritfromthegenericJSONvalue,JsValue.
ParsingaJsonString
valjson:JsValue=Json.parse(jsonString)
NavigatingintoaJsontree
AssoonaswehaveaJsValuewecannavigateintothetree.
valjson=Json.parse(jsonString)
valmaybeName=(json\"user"\name).asOpt[String]
valemails=(json\"user"\\"emails").map(_.as[String])
asOpt[T]thatwillreturnNoneifthevalueismissing.
Otherwisewecanuseas[T]thatwefailwithanexceptionifthevaluewasmissing.
ConvertingaScalavaluetoJson
valjsonNumber=Json.toJson(4)
valjsonArray=Json.toJson(Seq(1,2,3,4))
ItiscomplicatediftheSeqcontainssomeothervaluesthanInt,forexample:
Seq(1,"Carl",3,4)
valjsonArray=Json.toJson(Seq(
toJson(1),toJson("Carl"),toJson(3),toJson(4)
))
SerializingJson
valjsonString:String=Json.stringify(jsValue)
OtherOptions
…snip...
5.2HandlingandservingJSONrequests
defsayHi=Action{reqeust=>
request.body.asJson.map{json=>
(json\"name").asOpt[String].map{name=>
Ok("Hello"+name)
}.getOrElse{
BadRequest("Missingparameter[name]")
}
}.getOrElse{
BadRequest("ExpectingJsondata")
}
}
BetterandSimplertospecifyourownBodyParser
defsayHi=Action(parse.json){request=>
(request.body\"name").asOpt[String].map{name=>
Ok("Hello"+name)
}.getOrElse{
…snip...
}
}
WecanusecommandlinecURL
>curl
--header"Content-type:application/json"
--requestPOST
--data'{"name":"Carl"}'
http://localhost:9000/sayHi
ServingaJSONresponse
defsayHi=Action(parse.json){request=>
(request.body\"name").asOpt[String].map{name=>
Ok(toJson(
Map("status"->"OK","message"->("Hello"+name))
))
}.getOrElse{
BadRequest(toJson(
Map("status"->"KO","message"->"Missingparameter[name]")
))
}
}
6.WorkingwithXML
HandlinganXMLrequest
defsayHi=Action{request=>
request.body.asXml.map{xml=>
(xml\\"name"headOption).map(_.text).map{name=>
Ok("Hello"+name)
}.getOrElse{
BadRequest("Missingparameter[name]")
}
}.getOrElse{
BadRequest("ExpectingXMLdata")
}
}
defsayHi=Action(parse.xml){request=>
(request.body\\"name"headOption).map(_.text).map{name=>
Ok("Hello"+name)
}.getOrElse{
BadRequet("Missingparameter[name])
}
}
ServinganXMLresponse
defsayHi=Action(parse.xml){request=>
(request.body\\"name"headOption).map(_.text).map{name=>
Ok(<messagestatus="OK">Hello{name}</message>)
}.getOrElse{
BadRequest(<messagestatus="KO">Missingparameter[name]</message>)
}
}
7Handlingfileupload
Uploadingfilesinaformusingmultipart/form-data
@form(action=routes.Application.upload,'enctype->"multipart/form-data")
<inputtype="file"name="picture">
<p><inputtype="submit"></p>
}
defupload=Action(parse.multipartFormData){request=>
request.body.file("picture").map{picture=>
importjava.io.File
valfilename=picture.filename
valcontentType=picture.contentType
picture.ref.moveTo(newFile("/tmp/picture"))
Ok("Fileuploaded")
}.getOrElse{
Redirect(routes.Application.index).flashing(
"error"->"Missingfile"
)
}
}
TherefattributegivesmeareferencetoaTemporaryFile.
Directfileupload
AJAXuploadthefileasynchronouslyinaform.
WritingOurOwnBodyParser
8.AccessinganSQLdatabase
8.1ConfiguringandUsingJDBC
SQLitedatabaseengineconnectionproperties
db.default.driver=org.sqlite.JDBC
db.default.url="jdbc:sqlite:/path/to/db-file"
AccessingtheJDBCdatasource
Theplay.api.dbpackageprovidesaccesstotheconfigureddatasources:
valds=DB.getDataSource()
ObtainingaJDBCconnection
valconnection=DB.getConnection()
Weneedtocallcloseafterweusetheconnection.Wecandothatinanotherway:
//access"default"database
DB.withConnection{conn=>
//dowhateveryouneedwiththeconnection
}
//access"orders"databaseinsteadof"default"
DB.withConnection("orders"){conn=>
//
}
8.2Anorm,simpleSQLdataaccess
PlayincludesasimpledataaccesslayercalledAnormthatusesplainSQLtointeractwiththedatabaseandprovidesanAPItoparseandtransformtheresultingdatasets.
mysqlconfiguration
db.default.driver=com.mysql.jdbc.Driver
db.default.url="jdbc:mysql://localhost/world"
db.default.user=root
db.default.password=111111
Overview
ItcanfeelstrangetoreturntoplainoldSQLtoaccessanSQLdatabasethesedays.JavaDevelopersaccustomedtousingahigh-levelObjectRelationalMapperlikeHibernate.
Butwethinkthesetoolsarenotneededatallwhenyouhavethepowerofahigh-levelprogramminglanguagelikeScala.Onthecontrary,theywillquicklybecomecounter-productive.
UsingJDBCisapain,butweprovideabetterAPI
Youdon'tneedanotherDSLtoaccessrelationaldatabases
SQLisalreadythebestDSLforaccessingrelationaldatabases.Wedon'tneedtoinventsomethingnew.
AtypesafeDSLtogenerateSQLisamistake
TakeControlofyourSQLcode
ExecutingSQLqueries
importanorm._
DB.withConnection{implicitc=>
valresult:Boolean=SQL("Select1").execute()
}
Theexecute()methodreturnsaBooleanvalueindicatingwhethertheexecutionwassuccessful.
Toexecuteanupdate,youcanuseexecuteUpdate(),whichreturnsthenumberofrowsupdated.
valresult:Int=SQL("deletefromCitywhereid=99").executeUpdate()
Ifweplantoinsertdatathathasanauto-generatedLongprimarykey,youcancallexecuteInsert().
valid:Int=SQL("insertintoCity(name,country)values({name},{country}")
.on("Cambridge","NewZealand").executeInsert();
SinceScalasupportsmulti-linestrings,feelfreetousethemforcomplexSQLstatement:
valsqlQuery=SQL(
"""
select*fromCountryc
joinCountryLanguagelonl.CountryCode=c.Code
wherec.code='FRA';
"""
)
IfourSQLqueryneedsdynamicparameters,wecandeclareplaceholderslike{name}inthequerystring,andlaterassignavaluetothem:
SQL(
"""
select*fromCountryc
joinCountryLanguagelonl.CountryCode=c.Code
wherec.code={countryCode};
"""
).on("countryCode"->"FRA")
RetrievingdatausingtheStreamAPI
ThefirstwaytoaccesstheresultsofaselectqueryistousetheStreamAPI.
Whenyoucallapply()onanySQLstatement,youwillreceivealazyStreamofRowinstances,whereeachrowcanbeseenasadictionary:
//CreateanSQLquery
valselectCountries=SQL("Select*fromCountry")
//TransformtheresultingStream[Row]toaList[(String,String)]
valcountries=selectCountries().map(row=>
row[String]("code")->row[String]("name")
).toList
CountthenumberofCountryentriesinthedatabase,sotheresultsetwillbeasinglerowwithasinglecolumn:
valfirstRow=SQL("Selectcount(*)ascfromCountry").apply().head
//Nextgetthecontentofthe'c'columnasLong
valcountryCount=firstRow[Long]("c")
UsingPatternMatching
…snip…
Specialdatatypes
Clobs
SQL("Selectname,summaryfromCountry")().map{
caseRow(name:String,summary:java.sql.Clob)=>name->summary
}
Binary
SQL("Selectname,imagefromCountry")().map{
caseRow(name:String,image:Array[Byte])=>name->image
}
DealingwithNullablecolumns
IfacolumncancontainNullvaluesinthedatabaseschema,youneedtomanipulateitasanOptiontype.
SQL("Selectname,indepYearfromCountry")().collect{
caseRow(name:String,Some(year:int))=>name->year
}
IfwetrytoretrievethecolumncontentasIntdirectlyfromthedictionary
SQL("Selectname,indepYearfromCountry")().map{row=>
row[String]("name")->row[Int]("indepYear")
}
ThiswillproduceanUnexpectedNullableFound(COUNTRY.INDEPYEAR)exceptionifitencountersanullvalue.
WeneedtowritelikethistoanOption[Int]
SQL("Selectname,indepYearfromCountry")().map{row=>
row[String]("name")->row[Option[Int]]("indepYear")
}
References:
http://www.playframework.org/documentation/2.0.4/ScalaHome
http://www.playframework.org/documentation/2.0.4/ScalaXmlRequests