zio/zio-json

serialize option None field on a object as json null

wenhoujx opened this issue ยท 7 comments

hello i found closed ticket #70 which concludes with "None fields on an object is dropped", however my use case actually asks for those null to be present.

For example, the following test fails.

I wonder if there is a way to configure the null encoding behavior. ๐Ÿ™

  case class Foo(bar: Option[Bar])
  case class Bar(baz: Option[String])

    test("test encoding null") {
      assertTrue(Foo(Some(Bar(baz=None))).toJson == """{"bar":{"baz":null}}""")
    }

btw, i also found #638, which has a OptionAsJsonNull wrapper. But i don't control all of the classes to be JSON serialized, so i don't think this wrapper helps me.

@wenhoujx , could you explain the use case further? Usage of null in Scala code is generally considered an anti-pattern.

@Andrapyre hi, i am not trying to use null in scala, i am trying to use json null .

encoding: Option None -> json null
decoding: json null -> Option None.

it's a good idea to not encoding json null fields in a json object, however it's nice to be able to control that behavior.

we figured out we can use implicit codec, however this doesn't work for zio-json 0.2.0-M4, works on zio-json 0.6.2.

0.2.0-M4 is the last version for zio 1 ? is that LTS? if so i think there is a bug.

object JsonNull {
  import zio.json._  
  import zio.json.ast._
  import zio.json.internal._
  
  implicit def explicitlyNullOption[A](implicit A: JsonEncoder[A]): JsonEncoder[Option[A]] = new JsonEncoder[Option[A]] {

    def unsafeEncode(oa: Option[A], indent: Option[Int], out: Write): Unit = oa match {
      case None    => out.write("null")
      case Some(a) => A.unsafeEncode(a, indent, out)
    }

    // This is the only override we need to produce the behavior, but 
    // unfortunately I did't see a clean way to pull the default
    // JsonEncoder[Option[A]] and override only this.  YMMV
    override def isNothing(oa: Option[A]): Boolean = false

    override final def toJsonAST(oa: Option[A]): Either[String, Json] =
      oa match {
        case None    => Right(Json.Null)
        case Some(a) => A.toJsonAST(a)
      }
  }
}  

object Codecs  {
  import JsonNull.explicitlyNullOption // import this at implicit macro expansion. 
  lazy implicit val codecBar = DeriveJsonCodec.gen[Bar]
  lazy implicit val codecFoo = DeriveJsonCodec.gen[Foo]
}

/bounty $100 for making it configurable in some nice and consistent way.

๐Ÿ’Ž $100 bounty โ€ข ZIO

Steps to solve:

  1. Start working: Comment /attempt #1085 with your implementation plan
  2. Submit work: Create a pull request including /claim #1085 in the PR body to claim the bounty
  3. Receive payment: 100% of the bounty is received 2-5 days post-reward. Make sure you are eligible for payouts

Thank you for contributing to zio/zio-json!

Add a bounty โ€ข Share on socials

Attempt Started (GMT+0) Solution
๐ŸŸข @987Nabil #1100

๐Ÿ’ก @987Nabil submitted a pull request that claims the bounty. You can visit your bounty board to reward.

๐ŸŽ‰๐ŸŽˆ @987Nabil has been awarded $100! ๐ŸŽˆ๐ŸŽŠ