Yadukrishnan
Scribblings of an introvert

Scribblings of an introvert

Json parsing using Json4s in Scala

Yadukrishnan's photo
Yadukrishnan
·Jul 1, 2020·

3 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Json4s is one of the most popular JSON parsing libraries in Scala. Json4s support the usage of multiple existing json libraries, like Jackson, Play-Json etc. In this short article, I want to show how we can implement JSON parsing, with custom serializers. I will be json4s with Jackson as the base.

To use Json4s, we need to add the below statement to build.sbt:

"org.json4s" %% "json4s-jackson" % "3.6.9"

Now, let’s see how we can write the methods for converting from and to json.

import org.json4s.jackson.Serialization._
import org.json4s._

trait JsonParser {

implicit lazy val serializationFormats: Formats = DefaultFormats

def fromJson[T](json: String)(implicit mf: Manifest[T]) = {
read[T](json)
}

def toJson(obj: AnyRef): String = {
write(obj)
}

}

With the above code, now we can convert a case class to JSON String and vice versa. But if the case class has a non-primitive field, say LocalDate then the above code will fail. In such case, we need to provide a custom serializer to the Formats.

implicit lazy val serializationFormats: Formats = new DefaultFormats {} ++ customSerializers

where, customSerializers can be implemented as below:

object CustomSerializers {

private val dateFormatter \= DateTimeFormatter.ofPattern("dd-MM-yyyy")
private val dateTimeFormatter \= DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")

case object LocalDateSerializer extends CustomSerializer[LocalDate](format => ( {
case JString(date) => LocalDate.parse(date, dateFormatter)
case JNull => null
}, {
case date: LocalDate => JString(date.format(dateFormatter))
}))

case object LocalDateTimeSerializer extends CustomSerializer[LocalDateTime](format => ( {
case JString(dt) => LocalDateTime.parse(dt, dateTimeFormatter)
case JNull => null
}, {
case dt: LocalDateTime => JString(dt.format(dateTimeFormatter))
}))
}

This above serializers will allow the serialization/deserialization of java.time.LocalDate and java.time.LocalDateTime fields.

Similarly, custom serializers can be created for case objects as well. For e.g: If there is an Enum implementation using sealed trait, and it is required to be serialized/deserialized to json format, then a CustomSerializer can be added for it, as shown below:

case object PeriodSerializer extends CustomSerializer[Period](format => ( {
case JString(dt) => {
dt match {
case "Day" => Period.Day
case "Week" => Period.Week
case "Month" => Period.Month
}
}
case JNull => null
}, {
case p: Period => JString(p.name)
}))

Now, let’s see how we can bring a field which is NOT part of a Case Class body. For e.g:

trait BaseEntity {
def desc: String
}
case class Item(itemCode: String, qty: Int, purchaseDate: LocalDate) extends BaseEntity {
override val desc: String = itemCode + ":" + qty
}

If the above case class Item is converted into json, the field desc, which is in the body of case class will not be available in the json string. To get such fields, we need to define a FieldSerializer and add to the DefaultFormats.

implicit lazy val serializationFormats: Formats = new DefaultFormats {} ++ customSerializers + FieldSerializer[BaseEntity]()

So, in this article we have seen how to use json4s to parse json strings, and how to write Custom Serializers for complex data types. The complete source code for the demo project is available in GitHub.

 
Share this