-
Notifications
You must be signed in to change notification settings - Fork 1
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
use circe as serde lib for json: 200 #214
use circe as serde lib for json: 200 #214
Conversation
JaCoCo model module code coverage report - scala 2.13.11
|
JaCoCo agent module code coverage report - scala 2.13.11
|
JaCoCo server module code coverage report - scala 2.13.11
|
Release notes: Added |
import java.time.format.DateTimeFormatter | ||
import java.util.UUID | ||
|
||
object SerializationUtils { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole object SerializationUtils
should be removed (or kept only for third party types serde implicits). It's customary to define serde implicits in companion objects - for default de/serialization. If needed one could then provide alternative implementation(s) either in direct scope where the de/serialization is performed or create yet another object for non-default implementations inside the companion object and in import into direct scope only the desired implementation (the preffered approach in my opinion).
A simple example would be:
import io.circe.{Decoder, Encoder, HCursor, Json}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
case class Person(name: String, age: Int)
object Person {
implicit val defaultPersonEncoder: Encoder[Person] = deriveEncoder[Person]
implicit val defaultPersonDecoder: Decoder[Person] = deriveDecoder[Person]
object JsonImplicits {
implicit val nonDefaultPersonEncoder: Encoder[Person] = new Encoder[Person] {
final def apply(a: Person): Json = Json.obj(
("name", Json.fromString(a.name)),
("isAdult", Json.fromBoolean(a.age >= 18))
)
}
implicit val nonDefaultPersonDecoder: Decoder[Person] = new Decoder[Person] {
final def apply(c: HCursor): Decoder.Result[Person] =
for {
name <- c.downField("name").as[String]
isAdult <- c.downField("isAdult").as[Boolean]
} yield {
val age = if (isAdult) 18 else 17
Person(name, age)
}
}
}
}
Usage would look like this:
import io.circe.syntax._
import io.circe.parser._
val person = Person("John Doe", 20)
// Using default serde from companion (always available in direct scope without the need to import it)
val defaultJson = person.asJson // implicitly looked up from companion Person.defaultPersonEncoder
// Using non-default serde, passed implicitly
import Person.JsonImplicits.nonDefaultPersonEncoder
val nonDefaultJson = person.asJson
// Using non-default serde, passed explicitly
val nonDefaultJson = person.asJson(Person.JsonImplicits.nonDefaultPersonEncoder)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the clarification.
…/github.com/AbsaOSS/atum-service into feature/#200-Use-circe-as-serde-lib-forjson
@@ -22,7 +22,9 @@ import sttp.client3._ | |||
import sttp.model.Uri | |||
import za.co.absa.atum.agent.exception.AtumAgentException.HttpException | |||
import za.co.absa.atum.model.dto.{AdditionalDataSubmitDTO, AtumContextDTO, CheckpointDTO, PartitioningSubmitDTO} | |||
import za.co.absa.atum.model.utils.SerializationUtils | |||
import io.circe.generic.auto._ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems unused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we have defined our serde in companions, therefore we don't need generic.auto.
import za.co.absa.atum.model.dto.PartitioningDTO | ||
import scala.collection.immutable.Seq |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused
@@ -16,18 +16,19 @@ | |||
|
|||
package za.co.absa.atum.server.api.http | |||
|
|||
//import io.circe.generic.auto._ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dead code
@@ -16,18 +16,18 @@ | |||
|
|||
package za.co.absa.atum.server.api.http | |||
|
|||
//import io.circe.generic.auto._ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dead code
@@ -16,18 +16,18 @@ | |||
|
|||
package za.co.absa.atum.server.api.http | |||
|
|||
//import io.circe.generic.auto._ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dead code
def asJson[T: Encoder](obj: T): String = { | ||
obj.asJson.noSpaces | ||
} | ||
|
||
def asJsonPretty[T: Encoder](obj: T): String = { | ||
obj.asJson.spaces2 | ||
} | ||
|
||
def fromJson[T: Decoder](jsonStr: String): T = { | ||
decode[T](jsonStr) match { | ||
case Right(value) => value | ||
case Left(error) => throw new RuntimeException(s"Failed to decode JSON: $error") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For discussion:
I would rename these methods to
asJsonString
, asJsonStringPretty
and fromJsonString
To make ti clear it accepts String
, instead of Circe's Json
and actually not to confuse with functions of the same name in Circe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yah, I can change them. I don't see anything wrong with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benedeki good suggestion, it's better to have them named differently than methods in Circe.
…/functions/CreateOrUpdateAdditionalData.scala Co-authored-by: David Benedeki <[email protected]>
Co-authored-by: David Benedeki <[email protected]>
@@ -47,19 +48,19 @@ class HttpDispatcher(config: Config) extends Dispatcher(config: Config) with Log | |||
override protected[agent] def createPartitioning(partitioning: PartitioningSubmitDTO): AtumContextDTO = { | |||
val request = commonAtumRequest | |||
.post(createPartitioningEndpoint) | |||
.body(SerializationUtils.asJson(partitioning)) | |||
.body(partitioning.asJson.noSpaces) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this a bit inconsistent. If you intent to have the JsonUtils
class with fromJsonString
method, then I think you should use the asJsonString
method instead of calling asJson.noSpaces
. And this way it should be done all over the code base.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively we could use extension methods.
object JsonSyntaxExtensions {
implicit class JsonSerializationSyntax[T: Encoder](val obj: T) extends AnyVal {
def asJsonString: String = obj.asJson.noSpaces
def asJsonStringPretty: String = obj.asJson.spaces2
}
implicit class JsonDeserializationSyntax[T: Decoder](val jsonStr: String) extends AnyVal {
def as: T = {
decode[T](jsonStr) match {
case Right(value) => value
case Left(error) => throw new RuntimeException(s"Failed to decode JSON: $error")
}
}
}
}
val person = Person("Alice", 30)
// Convert a Person instance to a compact JSON string
val jsonString = person.asJsonString
println(jsonString) // {"name":"Alice","age":30}
// Convert a Person instance to a pretty JSON string
val jsonPrettyString = person.asJsonStringPretty
val jsonStr = """{"name":"Bob","age":25}"""
// Convert a JSON string back to a Person instance
val personFromJson = jsonStr.as[Person]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This I tried to do, but it keeps saying that JsonSerializationSyntax
and JsonDeserializationSyntax
must have one input and it must be val. So, I switched back to our old implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working solution
object JsonSyntaxExtensions {
implicit class JsonSerializationSyntax[T: Encoder](val obj: T) {
def asJsonString: String = obj.asJson.noSpaces
def asJsonStringPretty: String = obj.asJson.spaces2
}
implicit class JsonDeserializationSyntax(val jsonStr: String) {
def as[T: Decoder]: T = {
decode[T](jsonStr) match {
case Right(value) => value
case Left(error) => throw new RuntimeException(s"Failed to decode JSON: $error")
}
}
}
}
implicit class JsonSerializationSyntax[T: Encoder](val obj: T) { | ||
def asJsonString: String = obj.asJson.noSpaces | ||
|
||
def asJsonStringPretty: String = obj.asJson.spaces2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need this 'pretty' version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not used, I guess we don't need it until further notice.
@@ -62,7 +62,7 @@ object CreateCheckpointEndpointUnitTests extends ZIOSpecDefault with Endpoints w | |||
suite("CreateCheckpointEndpointSuite")( | |||
test("Returns expected CheckpointDTO") { | |||
val response = request | |||
.body(checkpointDTO1) | |||
.body(checkpointDTO1.asJsonString) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these conversions are not needed, it can be passed as not serialized ... the import sttp.client3.circe._ brings circeBodySerializer
method into scope which generate BodySerializer given an implicit Encoder in scope for a given type ...
5ecbe67
to
f0878b0
Compare
private implicit val showPgArray: Show[PgArray] = Show.fromToString | ||
|
||
implicit val getMapWithOptionStringValues: Get[Map[String, Option[String]]] = Get[Map[String, String]] | ||
.tmap(map => map.map { case (k, v) => k -> Option(v) }) | ||
|
||
private def circeJsonListToPGJsonArrayString(jsonList: List[CirceJson]): String = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't work... I am currently testing these implicits in fa-db
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'm checking it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good from my point of view.
The little suggestion is not worth its own commit. So once clarified the possible problem @salamonpavel pointed out, I am happy to approve and merge.
fromJson[AtumContextDTO]( | ||
handleResponseBody(response) | ||
) | ||
handleResponseBody(response).as[AtumContextDTO] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIny:
handleResponseBody(response).as[AtumContextDTO] | |
handleResponseBody(response).as[AtumContextDTO] |
def asJsonPretty[T: Encoder](obj: T): String = { | ||
obj.asJson.spaces2 | ||
} | ||
implicit class JsonSerializationSyntax[T: Encoder](obj: T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this. Makes it so clear at the usage places.. 😎
Release notes:
Circe Json
serde module library for Json for serialization.CirceJsonImplicit
class and defined implicit conversion for DTO's.jsonbArrayPutUsingString
withjsonbArrayPut
inDoobieImplicits
for both Json and Jsonb objects.SerislizationUtils
using circe format.SerializationUtilsUnitTest
.closes #200