4
votes

I want to have different names of fields in my case classes and in my JSON, therefore I need a comfortable way of renaming in both, encoding and decoding.

Does someone have a good solution ?

5

5 Answers

3
votes

You can use Custom key mappings via annotations. The most generic way is the JsonKey annotation from io.circe.generic.extras._. Example from the docs:

import io.circe.generic.extras._, io.circe.syntax._

implicit val config: Configuration = Configuration.default

@ConfiguredJsonCodec case class Bar(@JsonKey("my-int") i: Int, s: String)

Bar(13, "Qux").asJson
// res5: io.circe.Json = JObject(object[my-int -> 13,s -> "Qux"])

This requires the package circe-generic-extras.

2
votes
implicit val decodeFieldType: Decoder[FieldType] =
  Decoder.forProduct5("nth", "isVLEncoded", "isSerialized", "isSigningField", "type")
                     (FieldType.apply)

This is a simple way if you have lots of different field names. https://circe.github.io/circe/codecs/custom-codecs.html

1
votes

You can use the mapJson function on Encoder to derive an encoder from the generic one and remap your field name.

And you can use the prepare function on Decoder to transform the JSON passed to a generic Decoder.

You could also write both from scratch, but it may be a ton of boilerplate, those solutions should both be a handful of lines max each.

0
votes

Here's a code sample for Decoder (bit verbose since it won't remove the old field):

  val pimpedDecoder = deriveDecoder[PimpClass].prepare {
    _.withFocus {
      _.mapObject { x =>
        val value = x("old-field")
        value.map(x.add("new-field", _)).getOrElse(x)
      }
    }
  }
0
votes

The following function can be used to rename a circe's JSON field:

import io.circe._

object CirceUtil {
  def renameField(json: Json, fieldToRename: String, newName: String): Json =
    (for {
      value <- json.hcursor.downField(fieldToRename).focus
      newJson <- json.mapObject(_.add(newName, value)).hcursor.downField(fieldToRename).delete.top
    } yield newJson).getOrElse(json)
}

You can use it in an Encoder like so:

implicit val circeEncoder: Encoder[YourCaseClass] = deriveEncoder[YourCaseClass].mapJson(
  CirceUtil.renameField(_, "old_field_name", "new_field_name")
)

Extra

Unit tests

import io.circe.parser._
import org.specs2.mutable.Specification

class CirceUtilSpec extends Specification {

  "CirceUtil" should {
    "renameField" should {
      "correctly rename field" in {
        val json = parse("""{ "oldFieldName": 1 }""").toOption.get
        val resultJson = CirceUtil.renameField(json, "oldFieldName", "newFieldName")
        resultJson.hcursor.downField("oldFieldName").focus must beNone
        resultJson.hcursor.downField("newFieldName").focus must beSome
      }

      "return unchanged json if field is not found" in {
        val json = parse("""{ "oldFieldName": 1 }""").toOption.get
        val resultJson = CirceUtil.renameField(json, "nonExistentField", "newFieldName")
        resultJson must be equalTo json
      }
    }
  }
}