Skip to content

Commit

Permalink
Ways to configure the explicit encoding of empty options as null (#1085
Browse files Browse the repository at this point in the history
…) (#1100)

* Ways to configure the explicit encoding of empty options as null (#1085)
  • Loading branch information
987Nabil authored May 13, 2024
1 parent 61b7634 commit 7f5ed7e
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 11 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/site.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This file was autogenerated using `zio-sbt-website` via `sbt generateGithubWorkflow`
# This file was autogenerated using `zio-sbt-website` via `sbt generateGithubWorkflow`
# task and should be included in the git repository. Please do not edit it manually.

name: Website
Expand Down Expand Up @@ -29,8 +29,6 @@ jobs:
check-latest: true
- name: Check if the README file is up to date
run: sbt docs/checkReadme
- name: Check if the site workflow is up to date
run: sbt docs/checkGithubWorkflow
- name: Check artifacts build process
run: sbt +publishLocal
- name: Check website build process
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

[ZIO Json](https://github.com/zio/zio-json) is a fast and secure JSON library with tight ZIO integration.

[![Production Ready](https://img.shields.io/badge/Project%20Stage-Production%20Ready-brightgreen.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-json/workflows/CI/badge.svg) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-json_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-json_2.13/) [![ZIO JSON](https://img.shields.io/github/stars/zio/zio-json?style=social)](https://github.com/zio/zio-json)
[![Production Ready](https://img.shields.io/badge/Project%20Stage-Production%20Ready-brightgreen.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-json/workflows/CI/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-json_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-json_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-json_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-json_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-json-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-json-docs_2.13) [![ZIO JSON](https://img.shields.io/github/stars/zio/zio-json?style=social)](https://github.com/zio/zio-json)

## Introduction

Expand All @@ -25,7 +25,7 @@ The goal of this project is to create the best all-round JSON library for Scala:
In order to use this library, we need to add the following line in our `build.sbt` file:

```scala
libraryDependencies += "dev.zio" %% "zio-json" % "<version>"
libraryDependencies += "dev.zio" %% "zio-json" % "0.6.2"
```

## Example
Expand Down
16 changes: 12 additions & 4 deletions zio-json/shared/src/main/scala-2.x/zio/json/macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ final case class jsonField(name: String) extends Annotation
*/
final case class jsonAliases(alias: String, aliases: String*) extends Annotation

final class jsonExplicitNull extends Annotation

/**
* If used on a sealed class, will determine the name of the field for
* disambiguating classes.
Expand Down Expand Up @@ -212,7 +214,8 @@ final case class JsonCodecConfiguration(
sumTypeHandling: SumTypeHandling = WrapperWithClassNameField,
fieldNameMapping: JsonMemberFormat = IdentityFormat,
allowExtraFields: Boolean = true,
sumTypeMapping: JsonMemberFormat = IdentityFormat
sumTypeMapping: JsonMemberFormat = IdentityFormat,
explicitNulls: Boolean = false
)

object JsonCodecConfiguration {
Expand Down Expand Up @@ -554,6 +557,10 @@ object DeriveJsonEncoder {
name
}.getOrElse(if (transformNames) nameTransform(p.label) else p.label)
}

val explicitNulls: Boolean =
config.explicitNulls || ctx.annotations.exists(_.isInstanceOf[jsonExplicitNull])

lazy val tcs: Array[JsonEncoder[Any]] = params.map(p => p.typeclass.asInstanceOf[JsonEncoder[Any]])
val len: Int = params.length
def unsafeEncode(a: A, indent: Option[Int], out: Write): Unit = {
Expand All @@ -564,9 +571,10 @@ object DeriveJsonEncoder {

var prevFields = false // whether any fields have been written
while (i < len) {
val tc = tcs(i)
val p = params(i).dereference(a)
if (!tc.isNothing(p)) {
val tc = tcs(i)
val p = params(i).dereference(a)
val writeNulls = explicitNulls || params(i).annotations.exists(_.isInstanceOf[jsonExplicitNull])
if (!tc.isNothing(p) || writeNulls) {
// if we have at least one field already, we need a comma
if (prevFields) {
if (indent.isEmpty) out.write(",")
Expand Down
11 changes: 9 additions & 2 deletions zio-json/shared/src/main/scala-3/zio/json/macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ final case class jsonField(name: String) extends Annotation
*/
final case class jsonAliases(alias: String, aliases: String*) extends Annotation

/**
* Empty option fields will be encoded as `null`.
*/
final class jsonExplicitNull extends Annotation

/**
* If used on a sealed class, will determine the name of the field for
* disambiguating classes.
Expand Down Expand Up @@ -540,6 +545,8 @@ object DeriveJsonEncoder extends Derivation[JsonEncoder] { self =>
})
.toArray

val explicitNulls = ctx.annotations.exists(_.isInstanceOf[jsonExplicitNull])

lazy val tcs: Array[JsonEncoder[Any]] =
IArray.genericWrapArray(params.map(_.typeclass.asInstanceOf[JsonEncoder[Any]])).toArray

Expand All @@ -555,8 +562,8 @@ object DeriveJsonEncoder extends Derivation[JsonEncoder] { self =>
while (i < len) {
val tc = tcs(i)
val p = params(i).deref(a)

if (! tc.isNothing(p)) {
val writeNulls = explicitNulls || params(i).annotations.exists(_.isInstanceOf[jsonExplicitNull])
if (! tc.isNothing(p) || writeNulls) {
// if we have at least one field already, we need a comma
if (prevFields) {
if (indent.isEmpty) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ object ConfigurableDeriveCodecSpec extends ZIOSpecDefault {
case class CaseClass(i: Int) extends ST
}

case class OptionalField(a: Option[Int])

def spec = suite("ConfigurableDeriveCodecSpec")(
suite("defaults")(
suite("string")(
Expand Down Expand Up @@ -177,6 +179,21 @@ object ConfigurableDeriveCodecSpec extends ZIOSpecDefault {
)
}
)
),
suite("explicit nulls")(
test("write null if configured") {
val expectedStr = """{"a":null}"""
val expectedObj = OptionalField(None)

implicit val config: JsonCodecConfiguration =
JsonCodecConfiguration(explicitNulls = true)
implicit val codec: JsonCodec[OptionalField] = DeriveJsonCodec.gen

assertTrue(
expectedStr.fromJson[OptionalField].toOption.get == expectedObj,
expectedObj.toJson == expectedStr
)
}
)
)
}
10 changes: 10 additions & 0 deletions zio-json/shared/src/test/scala/zio/json/EncoderSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ object EncoderSpec extends ZIOSpecDefault {
) &&
assert(CoupleOfThings(0, None, true).toJsonPretty)(equalTo("{\n \"j\" : 0,\n \"b\" : true\n}")) &&
assert(OptionalAndRequired(None, "foo").toJson)(equalTo("""{"s":"foo"}"""))
assert(OptionalExplicitNullAndRequired(None, "foo").toJson)(equalTo("""{"i":null,"s":"foo"}"""))
},
test("sum encoding") {
import examplesum._
Expand Down Expand Up @@ -468,6 +469,15 @@ object EncoderSpec extends ZIOSpecDefault {
DeriveJsonEncoder.gen[OptionalAndRequired]
}

@jsonExplicitNull
case class OptionalExplicitNullAndRequired(i: Option[Int], s: String)

object OptionalExplicitNullAndRequired {

implicit val encoder: JsonEncoder[OptionalExplicitNullAndRequired] =
DeriveJsonEncoder.gen[OptionalExplicitNullAndRequired]
}

case class Aliases(@jsonAliases("j", "k") i: Int, f: String)

object Aliases {
Expand Down

0 comments on commit 7f5ed7e

Please sign in to comment.