Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codec derivation - dropNullValues #323

Open
kamilkloch opened this issue Oct 3, 2023 · 4 comments
Open

Codec derivation - dropNullValues #323

kamilkloch opened this issue Oct 3, 2023 · 4 comments
Labels
help wanted Extra attention is needed

Comments

@kamilkloch
Copy link

kamilkloch commented Oct 3, 2023

For now it is impossible to convenietly generate encoder that does not produce null values at the output.

Scenario:

@JsonCodec case class Params(p1: String, p2: Option[Int])
def process(a: Json): Unit = ???

val params = Params("abc", None)
process(json"""{"requestId": 123, "params": $params}""")

And I want the a not to contain null values.
I know I can:

  1. preprocess a in the process() method - apply dropNullValues, use customized printer, etc
  2. postprocess json where I use interpolation
  3. customize codec by .mapJson(.dropNullValues)

First and second one seems like wrong place to solve this issue - its like workaround for misbehaving codec.

Third option requires lots of bolierplate code: Switching from annotation to manual derivation (creation of companion objects, etc), and then mapping derived encoder output.

I believe most correct solution would be to add io.circe.generic.extras.Configuration.dropNullValues. Then one could use it with derived codecs as well as with @ConfiguredJsonCodec.

@zarthross zarthross added the help wanted Extra attention is needed label Nov 16, 2023
@djx314
Copy link

djx314 commented Dec 4, 2024

https://scastie.scala-lang.org/CQEocDLHTwS51Gte8lCasQ

图片

@kamilkloch Provide a reproduct.

import io.circe._
import io.circe.syntax._
import io.circe.generic.JsonCodec
import io.circe.generic.extras.semiauto._
import io.circe.generic.extras.Configuration

@JsonCodec
case class Params(p1: String, p2: Option[Int])

val jsonString1: String = """{ "p1": "foo" }"""
val jsonString2: String = """{ "p1": "foo", "p2": null }"""

for {
  json1 <- parser.parse(jsonString1)
  json2 <- parser.parse(jsonString2)
  data1 <- json1.as[Params]
  data2 <- json2.as[Params]
} yield {
  println(data1)
  println(data2)
  println("// === problem start ===")
  println(data1.asJson == json1)
  println(data1.asJson == json2)
  println("// === problem end ===")
}

Output:

Params(foo,None)
Params(foo,None)
// === problem start ===
false
true
// === problem end ===

I can do nothing but hack Encoder with dropNullValues to make

data1.asJson == json1

return true in this code snippet.

Workaround code:

case class Params(p1: String, p2: Option[Int])
object Params {
  implicit val codecJson: Codec[Params] = {
    val impl = deriveCodec[Params]
    Codec.from(impl, impl.mapJson(_.dropNullValues))
  }
}

@kamilkloch
Copy link
Author

@kamilkloch Provide a reproduct.
Yes, I do get the same result.

I believe most correct solution would be to add io.circe.generic.extras.Configuration.dropNullValues. Then one could use it with derived codecs as well as with @ConfiguredJsonCodec.

@plokhotnyuk
Copy link

plokhotnyuk commented Dec 5, 2024

Yet another solution is using of jsoniter-scala-circe's codec instantiated with a function to filter out nulls.

The overhead of encoding of such fields with nulls will be compensated by much more efficient serialization from jsoniter-scala-core under the hood. Also, you will get an ability of serialization immediately to byte arrays, java.nio.ByteBufers, java.io.OutputStream or preallocated byte arrays without intermediate strings, that will save yet more CPU cycles and memory bandwidth.

@djx314
Copy link

djx314 commented Dec 5, 2024

@kamilkloch Provide a reproduct.
Yes, I do get the same result.

I believe most correct solution would be to add io.circe.generic.extras.Configuration.dropNullValues. Then one could use it with derived codecs as well as with @ConfiguredJsonCodec.

@kamilkloch Yes, I think so too. io.circe.generic.extras.Configuration.dropNullValues is the final solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants