title: Encoder for update endpoint of a Rest API
body: <p>I'm <a href="https://github.com/ist-dsi/scala-keystone-client" rel="nofollow noreferrer">implementing a client</a> for the <a href="https://docs.openstack.org/api-ref/identity/v3/?expanded=update-user-detail#users" rel="nofollow noreferrer">Keystone API of Openstack</a>. For the Users I have the following classes:</p>
  <pre><code>import java.time.OffsetDateTime
  import io.circe.derivation.{deriveDecoder, deriveEncoder, renaming}
  import io.circe.{Decoder, Encoder}
  
  object User {
    object Update {
      implicit val encoder: Encoder[Update] = deriveEncoder(renaming.snakeCase)
    }
    case class Update(
      name: Option[String] = None,
      password: Option[String] = None,
      defaultProjectId: Option[String] = None,
      enabled: Option[Boolean] = None,
    )
  
    implicit val decoder: Decoder[User] = deriveDecoder(renaming.snakeCase)
  }
  final case class User(
    id: String,
    name: String,
    domainId: String,
    defaultProjectId: Option[String] = None,
    passwordExpiresAt: Option[OffsetDateTime] = None,
    enabled: Boolean = true,
  )
  </code></pre>
  <p>Where <code>User.Update</code> contains the possible fields to update a user. Updates are done using PATCH, or in other words they are partial. The encoders are being used in a class which has the methods to create, update, delete, get, and list the users. This service class uses the encoders in a http4s <code>EntityEncoder</code> with:</p>
  <pre><code>import io.circe.{Encoder, Printer}
  import org.http4s.{EntityDecoder, circe}
  val jsonPrinter: Printer = Printer.noSpaces.copy(dropNullValues = true)
  implicit def jsonEncoder[A: Encoder]: EntityEncoder[F, A] = circe.jsonEncoderWithPrinterOf(jsonPrinter)
  </code></pre>
  <p>My problem is how to implement the update for <code>defaultProjectId</code>. In the final json sent to the server the following cases are possible:</p>
  <ol>
  <li><p>Keep the current value of <code>defaultProjectId</code> (the json object does not contain the field <code>default_project_id</code>:</p>
  <pre><code>{
    (...)
  }
  </code></pre>
  </li>
  <li><p>Change the <code>defaultProjectId</code> to <code>an-id</code>:</p>
  <pre><code>{
    (...),
    &quot;default_project_id: &quot;an-id&quot;,
    (...)
  }
  </code></pre>
  </li>
  <li><p>Unset the <code>defaultProjectId</code>:</p>
  <pre><code>{
    (...),
    &quot;default_project_id: null,
    (...)
  }
  </code></pre>
  </li>
  </ol>
  <p>The current implementation: <code>defaultProjectId: Option[String] = None</code> + <code>dropNullValues</code> in the printer, models correctly the cases 1 and 2, but prevents case 3.</p>
  <p>Ideally I would have an ADT like:</p>
  <pre><code>sealed trait Updatable[+T]
  case object KeepExistingValue extends Updatable[Nothing]
  case object Unset extends Updatable[Nothing]
  case class ChangeTo[T](value: T) extends Updatable[T]
  </code></pre>
  <p>Usage example (probably in the future all fields would be <code>Updatable</code>s):</p>
  <pre><code>case class Update(
    name: Option[String] = None,
    password: Option[String] = None,
    defaultProjectId: Updatable[String] = KeepExistingValue,
    enabled: Option[Boolean] = None,
  )
  </code></pre>
  <p>But I can't find a clean way to encode this ADT. Attempted solutions and their problems (they all require not using the printer with <code>dropNullValues</code> in the update method):</p>
  <ol>
  <li><p><code>Unset</code> is special:</p>
  <pre><code>// The generic implementation of Updatable
  implicit def updatableEncoder[T](implicit valueEncoder: Encoder[T]): Encoder[Updatable[T]] = {
    case KeepExistingValue =&gt; Json.Null
    case Unset =&gt; Json.fromString(Unset.getClass.getName) // Or another arbitrary value
    case ChangeTo(value) =&gt; valueEncoder(value)
  }
  
  // In the service class
  def nullifyUnsets(obj: JsonObject): JsonObject = obj.mapValues {
    case json if json.asString.contains(Unset.getClass.getName) =&gt; Json.Null
    case json =&gt; json
  }
  def update(id: String, update: Update): F[Model] = {
    // updateEncoder is of type Encoder[Update]
    updateEncoder(update).dropNullValues.mapObject(nullifyUnsets)
    (...)
  }
  </code></pre>
  <p><strong>Pros:</strong></p>
  <ul>
  <li>Using the <code>dropNullValues</code> nicely handles the <code>KeepExistingValue</code> case.</li>
  <li>If the user invokes <code>dropNullValues</code> to derive the encoder the code still works.</li>
  </ul>
  <p><strong>Cons:</strong></p>
  <ul>
  <li>Because of <code>dropNullValues</code> the <code>Unset</code> case is meh.</li>
  <li>We iterate twice on the Json Object field/values, once for <code>dropNullValues</code> another for <code>mapObject</code>.</li>
  <li><code>Json.fromString(Unset.getClass.getName)</code> is arbitrary and can collide with a legit value for <code>T</code>, although very unlikely.</li>
  </ul>
  </li>
  <li><p><code>KeepExistingValue</code> is special:</p>
  <pre><code>// The generic implementation of Updatable
  implicit def updatableEncoder[T](implicit valueEncoder: Encoder[T]): Encoder[Updatable[T]] = {
    case KeepExistingValue =&gt; Json.fromString(Unset.getClass.getName) // Or another arbitrary value
    case Unset =&gt; Json.Null
    case ChangeTo(value) =&gt; valueEncoder(value)
  }
  
  // In the service class
  def dropKeepExistingValues(obj: JsonObject): JsonObject = obj.filter{
    case (_, json) =&gt; !json.asString.contains(Unset.getClass.getName)
  }
  def update(id: String, update: Update): F[Model] = {
    // updateEncoder is of type Encoder[Update]
    updateEncoder(update).mapObject(dropKeepExistingValues)
    (...)
  }
  </code></pre>
  <p><strong>Pros:</strong></p>
  <ul>
  <li>Simpler implementation, <code>updatableEncoder</code> implementation maps more directly to the needed Json.</li>
  <li>Just one pass over the Json Object field/values.</li>
  </ul>
  <p><strong>Cons:</strong></p>
  <ul>
  <li>If the programmer invokes <code>dropNullValues</code> to derive the encoder then the code stops working.</li>
  <li><code>Json.fromString(Unset.getClass.getName)</code> is arbitrary and can collide with a legit value for T, although very unlikely.</li>
  </ul>
  </li>
  </ol>
  <p>I'm sure I'm not the first one to hit this problem, but I can't search for it, the best I got is <a href="https://github.com/circe/circe/issues/584#issuecomment-346203481" rel="nofollow noreferrer">this comment</a>.</p>