Jan Swart
Jan Swart

Reputation: 7221

How to remove null values in list of objects using Circe

I'm trying to encode a list of objects using Circe, something that looks similar to:

val test = Seq(MyObject("hello", None, 1, 2, None)

I'm trying to parse this using Circe:

test.asJson

But this creates the JSON object:

[
  {
    name: "hello",
    someVal: null,
    someNum: 1,
    anotherNum: 2,
    anotherVal: null
  }
]

I've tried running asJson with .dropNullValues, but that doesn't seem to access the null values inside of the object. Is there a way to drop the null values inside of the objects?

I'm expecting something more like this:

[
  {
    name: "hello",
    someNum: 1,
    anotherNum: 2
  }
]

Upvotes: 10

Views: 6396

Answers (3)

chenhry
chenhry

Reputation: 526

Circe provides the function deepDropNullValues in the Json class.

Example: test.asJson.deepDropNullValues

Upvotes: 21

zkerriga
zkerriga

Reputation: 101

The answer that was suggested by @iva-kmm is good, but it can be done better!

This mechanic is already implemented in circe, just call it:

implicit val fooEnc: Encoder[Foo] = deriveEncoder[Foo].mapJson(_.dropNullValues) // or _.deepDropNullValues

Upvotes: 5

Iva Kam
Iva Kam

Reputation: 962

You see field: null because circe turns Option[T] to t.asJson on Some[T] and JsonNull on None, and default case class encoder just puts all fields to the JsonObject. In a way that circe uses to encode sealed trait family, it may use these null fields to distinguish classes like

sealed trait Foo
case class Bar(a: Option[String])
case class Baz(a: Option[String], b: Option[String])

So, if you really want to drop this information and need one-way conversion with information loss, you can map resulting Json to drop all and every null field with code like that:


implicit val fooEnc: Encoder[Foo] = deriveEncoder[Foo].mapJsonObject{jsonObj => jsonObj.filter{case (k,v) => !v.isNull}}

However, you should write such a custom codec for any class you want to drop null fields. To post-process your json, you can use fold on resulting json:

  val json: Json = ???
  json.fold[Json](
    Json.Null,
    Json.fromBoolean,
    {_.asJson},
    {_.asJson},
    {_.asJson},
    {jsonObj => jsonObj.filter{case (k,v) => !v.isNull}.asJson}
  )

or implement a custom folder.

Upvotes: 3

Related Questions