Skip to content

Latest commit

 

History

History
133 lines (90 loc) · 3.49 KB

README.md

File metadata and controls

133 lines (90 loc) · 3.49 KB

circe-empty-aware

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)

The problem:

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"
  }
}

Solution:

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