Akka http with json4s

Reading Time: 2 minutes

Akka Http uses the spray-json library for JSON support. But a few days back while working on a project which was using the json4s library for marshalling/unmarshalling, I got stuck during parsing of JSON request and extracting that parsed values into my target Scala data model.

Common transformations are summarized in the following picture.

Akka Http

Description :

  • I have a sample Scala web service created using Akka HTTP for performing CRUD operations which is not using any database for the time being as this blog focuses on json4s and Akka HTTP.
  • The user input contains objects which I want to extract cleanly. To keep things simpler, I have a Student case class which contains two fields roll number and name of the student. CRUD operations are performed on this Student data model.

Dependencies :

I am using SBT, so here is the relevant code from build.sbt



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


version := "0.1"
scalaVersion := "2.12.6"
libraryDependencies ++= Seq("com.typesafe.akka" %% "akka-http" % "10.0.11",
"org.json4s" %% "json4s-native" % "3.2.11",
"org.scalatest" %% "scalatest" % "3.0.1" % Test,
"org.mockito" % "mockito-core" % "2.11.0" % Test,
"com.typesafe.akka" %% "akka-http-testkit" % "10.1.1")
view raw

build.sbt

hosted with ❤ by GitHub

Scala data model as discussed above :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


import scala.collection.mutable.ListBuffer
case class Student(rollNum: Int, name: String)
trait Students {
val students = ListBuffer(Student(1, "Ayush"), Student(2, "deepankar"))
def fetchStudents: ListBuffer[Student] = students
def addStudent(student: Student): ListBuffer[Student] = {
student +: students
}
def updateStudent(student: Student): ListBuffer[Student] = {
val oldRecord = students.filter(_.rollNum == student.rollNum)
students = oldRecord
student +: students
}
def deleteStudent(rollNum: Int): ListBuffer[Student] = {
val deleteRecord = students.filter(_.rollNum == rollNum)
students = deleteRecord
}
}
view raw

Students.scala

hosted with ❤ by GitHub

To get Akka HTTP and json4s to play nicely together, I am going to implement a custom trait that defines some helper methods for marshalling/unmarshalling to and from JSON along with necessary implicit values. 



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


import org.json4s.{DefaultFormats, Formats, JNothing, JValue}
import org.json4s.native.JsonMethods.{parse => jParser}
import org.json4s.native.Serialization
import org.json4s.native.Serialization.{write => jWrite}
trait JsonHelper extends {
val EMPTY_STRING = ""
implicit val serialization: Serialization.type = Serialization
implicit val formats: Formats = DefaultFormats
/*
takes any type of value and serializes it to string
eg: val student = Student(1,"deepankar")
write(student) will serialize it to {"rollNum":1,"name":"deepankar"}
*/
def write[T <: AnyRef](value: T): String = jWrite(value)
/*
Any valid json can be parsed into internal AST(Abstract Syntax Tree) format.
eg : If this is our json string coming from a post request
val jsonString = {"rollNum":1,"name":"deepankar"}
parse(jsonString) will parse it to something like
JObject(List((rollNum,JInt(1)), (name,JString(deepankar))))
*/
protected def parse(value: String): JValue = jParser(value)
/*
extract method is used after parsing the json string, as parse method returns JValue so we
can map this JValue to our target scala data model
eg : val jsonString = {"rollNum":1,"name":"deepankar"}
val parseString = parse(jsonString)
extract(parseString) will return Student(?1,"deepankar")
*/
implicit protected def extractOrEmptyString(json: JValue): String = {
json match {
case JNothing => EMPTY_STRING
case data => data.extract[String]
}
}
}

Here’s our sample web server file that handles all CRUD related requests (GET, POST, PUT, DELETE) and responds with JSON. This file extends JsonHelper defined above and also shows the output generated by these helper methods to make your understanding even better.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.concurrent.ExecutionContextExecutor
//the usual config code required for an Akka Http server, mixing in our JsonHelper trait too
object Routes extends App with JsonHelper {
implicit val system: ActorSystem = ActorSystem("my-system")
implicit val materializer: ActorMaterializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext: ExecutionContextExecutor = system.dispatcher
val studentsClass = new Students
val route =
pathPrefix("demo") {
get {
path("fetchAllStudents") {
complete {
val addedStudents = studentsClass.fetchStudents
val jsonResponse = write(addedStudents)
/* json for list of Students
[{"rollNum":1,"name":"Ayush"},{"rollNum":2,"name":"deepankar"}]
*/
HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, jsonResponse))
}
}
} ~
post {
path("addStudent") {
entity(as[String]) { studentJson => {
complete {
val studentRecord = parse(studentJson).extract[studentsClass.Student]
/*
Student(3,randhir)
*/
val updatedList = studentsClass.addStudent(studentRecord)
val jsonResponse = write(updatedList)
HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, jsonResponse))
}
}
}
}
} ~
put {
//for updating student name student roll number has to be same as entered previously
path("updateStudent") {
entity(as[String]) { studentJson => {
complete {
val studentRecord = parse(studentJson).extract[studentsClass.Student]
val updatedList = studentsClass.updateStudent(studentRecord)
val jsonResponse = write(updatedList)
HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, jsonResponse))
}
}
}
}
} ~
delete {
path("deleteStudent" / IntNumber) { rollNum =>
complete {
val deleteRecord = studentsClass.deleteStudent(rollNum)
val jsonResponse = write(deleteRecord)
HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, jsonResponse))
}
}
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080")
}
view raw

Routes.scala

hosted with ❤ by GitHub

Feel free to download and run the project. In case you face any problem comment below for the help.

Till then keep sharing, reading, blogging.

knoldus-advt-sticker

Written by 

Deepankar is a software consultant having more than 1.5 years of experience. He likes to keep up with the trending technologies. He is familiar with languages such as C, C++, Java, Scala and is currently working on Scala and Akka Http. His hobbies includes sports, music, travelling and yoga.

1 thought on “Akka http with json4s2 min read

Comments are closed.