Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snake Case (or any Configuration-related problem) with Scala 3 #1694

Closed
aesteve opened this issue Mar 10, 2021 · 4 comments
Closed

Snake Case (or any Configuration-related problem) with Scala 3 #1694

aesteve opened this issue Mar 10, 2021 · 4 comments

Comments

@aesteve
Copy link

aesteve commented Mar 10, 2021

Hello, and thank you for all the effort you're putting in Circe, and Scala 3 migration.

I tried to measure the amount of effort needed to migrate one of my projects to Scala3.

I'm struggling a bit with JSON serialization since it involves snake_case transformations on case class attributes.
I used to use circe-generic-extras with ConfiguredJsonObject but I saw it will probably not be updated to Scala3 anytime soon, which I can understand.

Is there any other way (other than writing an Encoder by hand) to obtain an Encoder that uses snake_case for case class attributes?

Like deriving an Encoder and "mapping it" in some way?

Sorry if the question isn't clear enough, but I'm trying to find a workaround for this, and thanks again for your time and effort.

@heksesang
Copy link

You could use mapJson on a derived Encoder and run a snake_case transformation on each key in that Json object.

@travisbrown
Copy link
Member

Right, I think unfortunately right now your best bet on Scala 3 is something like this (tested on Scala 3):

scala> import io.circe.{Encoder, JsonObject}, io.circe.generic.semiauto.deriveEncoder

scala> import java.util.regex.Pattern

scala> val snakeCaseTransformation: String => String = s => {
     |   val basePattern: Pattern = Pattern.compile("([A-Z]+)([A-Z][a-z])")
     |   val swapPattern: Pattern = Pattern.compile("([a-z\\d])([A-Z])")
     |   val partial = basePattern.matcher(s).replaceAll("$1_$2")
     |   swapPattern.matcher(partial).replaceAll("$1_$2").toLowerCase
     | }
val snakeCaseTransformation: String => String = Lambda$6404/700040865@2b523359

scala> def snakeCaseIfy[A](encoder: Encoder.AsObject[A]): Encoder.AsObject[A] =
     |   encoder.mapJsonObject(obj =>
     |     JsonObject.fromIterable(obj.toIterable.map {
     |       case (k, v) => (snakeCaseTransformation(k), v) }
     |     )
     |   )
     | 
def snakeCaseIfy
  [A](encoder: io.circe.Encoder.AsObject[A]): io.circe.Encoder.AsObject[A]

scala> case class Foo(firstName: String, lastName: String)
// defined case class Foo

scala> implicit val encoderFoo: Encoder.AsObject[Foo] = snakeCaseIfy(deriveEncoder)
val encoderFoo: io.circe.Encoder.AsObject[Foo] = io.circe.Encoder$$anon$66@717a0dd4

scala> import io.circe.syntax._

scala> Foo("Foo", "McBar").asJson
val res0: io.circe.Json = {
  "first_name" : "Foo",
  "last_name" : "McBar"
}

@aesteve
Copy link
Author

aesteve commented Mar 17, 2021

Awesome detailed help.
Thank you!

@aesteve aesteve closed this as completed Mar 17, 2021
@aesteve
Copy link
Author

aesteve commented Mar 17, 2021

I'm sorry I only exposed the Serialization issue although I obviously have deserialization ones.

Is there a way to "unsakify" with a derivedDecoder?

Edit: https://gitter.im/circe/circe?at=5d542e282612bb718c685031 this withFocus seems like a good starting point for my investigations.

Something around these lines:

	private val snakePattern = "_([a-z\\d])".r

	private val snakeToCamel: String => String = s => {
		snakePattern.replaceAllIn(s, {m =>
			m.group(1).toUpperCase()
		})
	}
	
	private def unSnakeCaseIfy(o: JsonObject): JsonObject = 
		JsonObject.fromIterable(o.toVector.map { 
			case (k, v) => snakeToCamel(k) -> v 
		})
	
	def unSnakeCaseIfy[A](decoder: Decoder[A]): Decoder[A] = (c: HCursor) => {
		decoder.tryDecode(c.withFocus(_.mapObject(unSnakeCaseIfy)))
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants