This provide a way to make circe (https://github.com/circe/circe) encoders empty aware.
To use add to your build.sbt:
lazy val emptyAwareProject = RootProject( uri("git://github.com/opbokel/circe-empty-aware#v0.5") ) and add this on your root project definition .dependsOn(emptyAwareProject)
If you have several objects with lots of optional fields and/or empty arrays. Let's see the example below:
case class Documentation(userInstruction: Option[String] = None, quickTips: Option[String] = None)Documentation is a class with two optional fields
case class ItemOptDoc(name: String, documentation: Option[Documentation])An ItemOptDoc have a name and possibly a Documentation. This was made that way to allow the encoded json to support null documentation key and reduce payload.
And the encoders are something like this:
implicit val docEncoder = deriveEncoder[Documentation]
implicit val itemOptEncoder = deriveEncoder[ItemOptDoc]But this means that now I have one extra complexity and two ways to represent a empty Documentation object in scala. It makes the code more complex because of the JSON encoding.
/**
* Bouth represent empty Documentation
**/
ItemOptDoc("item 1", None)
ItemOptDoc("item 2", Some(Documentation()))So, the simpler (and probably best) way the make it in Scala is to declare item like this, without an optional field...
case class Item(name: String, documentation: Documentation)and solve the problem on the encoder. But writing custom encoders for each object is a boring process...
If we don't define a custom encoder we will produce a result like bellow. If you have a lot objects like item 1, it will increase your payload, memory consumption and JSON readability:
implicit val itemEncoder = deriveEncoder[Item]
itemEncoder(Item("Item 1", Documentation()))
itemEncoder(Item("Item 2", Documentation(Some("User instructions..."),Some("Good tips"))))Produced JSONs:
{
"name" : "Item 1",
"documentation" : {
"userInstruction" : null,
"quickTips" : null
}
}
{
"name" : "Item 2",
"documentation" : {
"userInstruction" : "User instructions...",
"quickTips" : "Good tips"
}
}Let the Encoder represent the Documentation Object as null if the Json generated by the Documentation encoder is empty.
A Value is considered empty if it is a empty array, null, a empty object or if all the keys on the object are associated with empty values.
Lets replace the Documentation encoder with a empty aware encoder:
import com.opb.circe.EmptyAwareEncoder._
implicit val docEncoder = deriveEncoder[Documentation].asEmptyAware()
itemEncoder(Item("Item 1", Documentation()))
itemEncoder(Item("Item 2", Documentation(Some("User instructions..."),Some("Good tips"))))The produced result:
//Item 1 documentation have been replaced by null
{
"name" : "Item 1",
"documentation" : null
}
//Item 2 still the same
{
"name" : "Item 2",
"documentation" : {
"userInstruction" : "User instructions...",
"quickTips" : "Good tips"
}
}Using it in conjunction with the io.circe.Printer option of dropNullValues = true we have the final output to the item 1 bellow:
{
"name" : "Item 1"
}Making a huge difference on a possible endpoint that returns thousands of items, reducing payload an saving memory.
Please see the Scaladoc on EmptyAwareEncoder to see all the usage possibilities