Implementing Config class containing Enum Map by PureConfig and Enumeratum - scala

I'm trying to read my config file into my case class whose one of attribute is a Map of Enumeratum key and Case Class value by using pureconfig and pureconfig-enumeratum libraries version 0.14.0 with scala 2.11.
When I change the Map key from Enumeratum key to String, it works, but it does not work with Enum key.
import enumeratum.EnumEntry.{Hyphencase}
import enumeratum._
import pureconfig.{ConfigSource}
import pureconfig.generic.auto._
import pureconfig.module.enumeratum._
object CheckPureConfig extends App {
private val myConf = ConfigSource.default.loadOrThrow[SsystemConf]
println(myConf)
}
case class SsystemConf(target: Map[Ssystem, MyConfig])
case class MyConfig(path: Ssystem, link: String)
sealed abstract class Ssystem(myField: String) extends EnumEntry with Hyphencase{
def printit() = myField
}
object Ssystem extends Enum[Ssystem] {
val values = findValues
case object MyEnumA extends Ssystem("testFieldEnum1")
case object MyEnumB extends Ssystem("testFieldEnum2")
}
And this is my application.conf
target {
my-enum-a= {
path : "samplepath1"
link : "samplehttp1"
}
my-enum-b = {
path : "samplepath2"
link : "samplehttp2"
}
}

You have to use configurable converter to tell pureconfig how to transform your enum to Map keys. You have genericMapReader for that:
implicit def enumMapReader[V: ConfigReader]: ConfigReader[Map[Ssystem, V]] =
genericMapReader { name =>
Ssystem.withNameOption(name)
.fold[Either[String, Ssystem]](Left(s"$name is not enum"))(Right(_))
}

Related

How to access scala annotation in scala code

I have defined a simple scala annoation and use it to annotate my case class Person:
package com.my
import scala.annotation.StaticAnnotation
#scala.annotation.meta.field
class Description(value: String) extends StaticAnnotation{
}
Then I use it in my Person class:
package com.my
import scala.beans.BeanProperty
case class Person(
#Description(value = "name00")
name: String,
#Description(value = "age00")
age: Int,
#BeanProperty
xyz: String = "xyz"
)
object Person {
def main(args: Array[String]): Unit = {
val p = Person("abc", 21)
classOf[Person].getDeclaredFields.foreach {
field =>
field.setAccessible(true)
val name = field.getName
val value = field.get(p)
//annotations always return empty array
val annotations = field.getDeclaredAnnotations
annotations.foreach {
annotation =>
val tpe = annotation.annotationType()
println(tpe)
}
println(s"name is $name, value is: $value")
}
}
}
In the main method of Person object, the annotations array is always empty,
I would like to ask how to get the annoation information defined on the field.
Firstly, annotations written in Scala are accessible in sources (if the annotations extend scala.annotation.Annotation) and class files (if if the annotations extend scala.annotation.StaticAnnotation). In order to be accessible at runtime the annotations must be written in Java
import java.lang.annotation.*;
#Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Description {
String value();
}
How to use Scala annotations in Java code
Why annotations written in Scala are not accessible at runtime?
https://www.reddit.com/r/scala/comments/81qzs2/how_to_write_annotations_in_scala/
Alternatively you can use the original #Description (written in Scala and extending StaticAnnotation) but then you have to access it at runtime via Scala reflection rather than Java reflection.
Secondly, you misuse meta-annotations (#scala.annotation.meta.field). They should annotate not the definition of #Description but its applications.
import scala.annotation.meta.field
case class Person(
#(Description #field)(value = "name00")
name: String,
#(Description #field)(value = "age00")
age: Int,
#BeanProperty
xyz: String = "xyz"
)
Annotating case class parameters
How can I reflect on a field annotation (Java) in a Scala program?
Calling a method from Annotation using reflection
Output:
interface Description // appears
name is name, value is: abc
interface Description // appears
name is age, value is: 21
name is xyz, value is: xyz
With Scala reflection you can do
import scala.annotation.StaticAnnotation
import scala.beans.BeanProperty
import scala.annotation.meta.field
import scala.reflect.runtime.universe._
class Description(value: String) extends StaticAnnotation
case class Person(
#(Description #field)(value = "name00")
name: String,
#(Description #field)(value = "age00")
age: Int,
#BeanProperty
xyz: String = "xyz"
)
def main(args: Array[String]): Unit = {
val p = Person("abc", 21)
typeOf[Person].decls
.collect { case t: TermSymbol if t.isVal => t.annotations }
.foreach(println)
}
//List(Description #scala.annotation.meta.field("name00"))
//List(Description #scala.annotation.meta.field("age00"))
//List(scala.beans.BeanProperty)

Tapir Custom Codec

I am stuck at a place, I am using scala, tapir and circe.
sealed abstract class S1Error extends Product with Serializable
object S1Error {
final case class SError(error: SMError) extends S1Error
}
sealed abstract class SMError(message: String)
object SMError {
final case class SVError(message: String) extends SMError(message)
}
For tapir errorOut I am using this
val schemaVersionError: EndpointOutput.StatusMapping[SError] = statusMappingValueMatcher(
StatusCode.BadRequest,
jsonBody[SError]
.description("XXXX.")
) {
case SMError(SVError(_)) => true
case _ => false
}
Now because of this structure, the API result I get is
{
"error": {
"SVError": {
"message": "XXXXG"
}
}
}
where as ideally I would want a response as
"message": "XXXXG"
I can not change the error structure.
Is there a way to wrap this error using a custom codec to get the result as required.
Tapir codec is derived from Circe's decoder and encoder.
What you see is the default way of encoding case classes by circe.
Circe provides the possibility to encode case classes the way you described with deriveUnwrappedEncoder from circe-generic-extras. Unfortunately, it doesn't compile for SMError (probably derivation mechanism gets confused by your abstract class hierarchy).
What you can do is just creating encoder manually:
sealed abstract class S1Error extends Product with Serializable
object S1Error {
final case class SError(error: SMError) extends S1Error
implicit val encoder: Encoder[SError] = Encoder[SMError].contramap(_.error)
// or you can use deriveUnwrappedEncoder from circe-generic-extras:
// implicit val encoder: Encoder[SError] = deriveUnwrappedEncoder
}
//I also needed to make message a field in SMError
sealed abstract class SMError(val message: String)
object SMError {
final case class SVError(override val message: String) extends SMError(message)
implicit val encoder: Encoder[SMError] = Encoder.encodeJsonObject.contramap{s => JsonObject("message" -> s.message.asJson)}
}
Response now looks like:
{
message": "XXXXG"
}

How do I get circe to have an either/or output scenario for the generated Json?

Here's what I intend - let's say I have a field called medical_payments - it can "either" be a limit if one elects or waived
{
"medical_payments":
{
"limit_value":"one_hundred"
}
}
If it's elected as a waiver then it should be:
{
"medical_payments":
{
"waived":true
}
}
So far here's what I have:
sealed trait LimitOrWaiver
case class Limit(limit_key: String) extends LimitOrWaiver
case class Waived(waived: Boolean) extends LimitOrWaiver
case class Selection(medical_payments: LimitOrWaiver)
Sample data:
Selection(medical_payments = Limit("one_hundred")).asJson
Output:
{
"medical_payments":
{
"Limit": { "limit_value":"one_hundred" } // additional object added
}
}
Similarly for Selection(medical_payments = Waived(true)).asJson an additional Waived:{...} is added to the Json.
I'd like it to be an either/or. What's the best way to achieve this?
The only way that I've been able to think of (not to my liking) is to use forProductN functions per the doc and manually do all this - but it's way to cumbersome for a large Json.
You can almost accomplish this with generic derivation using the configuration in generic-extras:
sealed trait LimitOrWaiver
case class Limit(limitValue: String) extends LimitOrWaiver
case class Waived(waived: Boolean) extends LimitOrWaiver
case class Selection(medicalPayments: LimitOrWaiver)
import io.circe.generic.extras.Configuration, io.circe.generic.extras.auto._
import io.circe.syntax._
implicit val codecConfiguration: Configuration =
Configuration.default.withDiscriminator("type").withSnakeCaseMemberNames
And then:
scala> Selection(medicalPayments = Limit("one_hundred")).asJson
res0: io.circe.Json =
{
"medical_payments" : {
"limit_value" : "one_hundred",
"type" : "Limit"
}
}
(Note that I've also changed the Scala case class member names to be Scala-idiomatic camel-case, and am handling the transformation to snake-case in the configuration.)
This isn't exactly what you want, since there's that extra type member, but circe's generic derivation only supports round-trip-able encoders / decoders, and without a discriminator of some kind—either a member like this or the extra object layer you point out in the question—it's impossible to round-trip values of arbitrary ADTs through JSON.
This might be fine—you might not care about the extra type in your object. If you do care, you can still use derivation with a little extra work:
import io.circe.generic.extras.Configuration, io.circe.generic.extras.auto._
import io.circe.generic.extras.semiauto._
import io.circe.ObjectEncoder, io.circe.syntax._
implicit val codecConfiguration: Configuration =
Configuration.default.withDiscriminator("type").withSnakeCaseMemberNames
implicit val encodeLimitOrWaiver: ObjectEncoder[LimitOrWaiver] =
deriveEncoder[LimitOrWaiver].mapJsonObject(_.remove("type"))
And:
scala> Selection(medicalPayments = Limit("one_hundred")).asJson
res0: io.circe.Json =
{
"medical_payments" : {
"limit_value" : "one_hundred"
}
}
If you really wanted you could even make this automatic, so that type would be removed from any ADT encoders you derive.

using Enum in traits in scala

i have a trait named UserT and a class DirectUserT extending the trait
i want to add enum in the trait so that child classes can use it
i have made a scala Object UserStatus which extends Enumeration
now i want to have this enum in my trait so that child classes can use it but i dont know how should i do is ?
my enum object
package testlogic
object UserStatus extends Enumeration{
type UserStatus = Value
val ACTIVE , INACTIVE , BLOCKED , DELETED = Value
}
here is my code for UserT
package testlogic
import testlogic.UserStatus._
trait UserT {
var name : String = ""
def setName( aName: String)= {
name = aName
}
def getName : String = {
name
}
}
DirectUserT.scala
package testlogic
class DirectuserT extends UserT {
var currentStatus =BLOCKED
//println(currentStatus)
}
eclipse shows error on BLOCKED
Please help
You need to add
import testlogic.UserStatus._
to you class DirectUserT.scala
Or add it within your trait:
trait UserT {
import testlogic.UserStatus._
}

Serialize class as its one member with Jackson

Sometimes the class has only one member to serialize (other members are transient), and I would like to serialize and deserialize this class as its only member.
Consider following code:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
case class Content(i:Seq[Int])
case class Wrap(content:Content)
object Main extends App {
val om = new ObjectMapper() with ScalaObjectMapper {
registerModule(new DefaultScalaModule)
}
val roots = Wrap(Content(Seq(1,2,3)))
val out = om.writeValueAsString(roots)
println(out)
val test = om.readValue(out, classOf[Wrap])
println(test)
}
The result of serialization of Wrapis {"content":{"i":[1,2,3]}}.
I would like to get {"i":[1,2,3]} only. I guess I could do this with custom serializer/deserializer, but given in real case the content is a complex class, this would mean I would have to serialize the content manually, if I am not mistaken. I would prefer some more straightforward solution.
Is it possible to "delegate" the serialization/deserialization to a member/constructor parameter?
It can be done using converters, which can be used to modify Jackson behaviour using JsonSerialize and JsonDeserialize properties.
import com.fasterxml.jackson.databind.annotation.{JsonSerialize, JsonDeserialize}
import com.fasterxml.jackson.databind.util.StdConverter
#JsonDeserialize(converter=classOf[WrapConverterDeserialize])
#JsonSerialize(converter=classOf[WrapConverterSerialize])
case class Wrap(content:Content)
class WrapConverterDeserialize extends StdConverter[Content,Wrap] {
override def convert(value: Content) = new Wrap(value)
}
class WrapConverterSerialize extends StdConverter[Wrap,Content] {
override def convert(value: Wrap) = value.content
}