7

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 ?

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
tobi
  • 329
  • 4
  • 11

5 Answers5

9

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.

trand
  • 126
  • 2
  • 5
  • Please note that you'll not be allowed to declare `implicit` values at the top level as mentioned in the answered. However, you can add to the bottom something like this: ```object Bar { implicit val config: Configuration = Configuration.default }``` – Bishwajit Purkaystha Jul 05 '22 at 10:40
2

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)
      }
    }
  }
Leammas
  • 167
  • 6
2
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

Mebin Joe
  • 2,172
  • 4
  • 16
  • 22
1

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.

C4stor
  • 8,355
  • 6
  • 29
  • 47
  • Where can I find more examples of using Circe to do JSON schema evolution? e.g. a new field in Scala model but not in JSON, a removed field in Scala model but in JSON, a renamed field, etc. – pumpump Oct 04 '18 at 14:46
  • I think the official documentation is quite nice : https://circe.github.io/circe/ – C4stor Oct 04 '18 at 16:01
  • 1
    When evolution getting harder we prefer to split case classes to common model an versions of JSON representation. For transformation between them we use Chimney's macros: https://github.com/scalalandio/chimney It is much easy and safer way than writing and support of custom codecs. – Andriy Plokhotnyuk Apr 06 '19 at 05:02
1

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
      }
    }
  }
}
LLCampos
  • 295
  • 4
  • 17