How to serialize a case class with a protobuf field? - scala

I have the following code that serializes a case class with an enum field to json (shorten for readability reasons). The code defines an EnumNameSerializer formatter:
import org.json4s.ext.EnumNameSerializer
import org.json4s.{DefaultFormats, Formats}
import org.json4s.jackson.JsonMethods.parse
// Class containing an enum
object EnumField extends Enumeration {
val value1, value2, value3 = Value
}
case class ClassWithProto(field: String,
enumField: EnumField)
object Serializer {
// Formatter
implicit val formats: Formats = DefaultFormats + new EnumNameSerializer(EnumField)
...
}
I'd like to replace the enum field with a protobuf field:
// Proto definition
message Value1 {}
message Value2 {}
message Value3 {}
message ProtoField {
oneof sealed_value_optional {
Value1 val1 = 1;
Value2 val2 = 2;
Value3 val3 = 3;
}
}
// New case class
case class ClassWithProto(field: String,
protoField: ProtoField)
How can I replace the old EnumNameSerializer in the formatter with a serializer that can serialize the protobuf field?
I thought about converting the protobuf class to an enum (and vice-versa) but this is an inefficient solution.

Related

How to pick a field's type based off of another field's value when reading JSON using Play (Scala)?

New to Scala and Play and running into a problem where I have the following:
case class Media(
name: String,
id: Id,
type: String,
properties: PodcastProperties
)
object Media {
implicit val format: OFormat[Media] = Json.format[Media]
case class PodcastProperties(
x: Int,
y: DateTime,
z: String
)
object PodcastProperties {
implicit val format: OFormat[PodcastProperties] = Json.format[PodcastProperties]
Say I want to define Media to accept different media types. Let's say I have a Json Media object, and it's type is "newspaper" and it's properties should be parse using "NewspaperProperties"
case class NewspaperProperties(
Title: String,
Publisher: String
)
object NewspaperProperties {
implicit val format: OFormat[NewspaperProperties] = Json.format[NewspaperProperties]
How can I define Media, so it can parse the "type" field, and then read the "properties" field correctly using the right Json parser?
You need to defined the media properties as sealed family.
import play.api.libs.json._
import java.time.OffsetDateTime
sealed trait MediaProperties
case class NewspaperProperties(
title: String, // Do not use 'Title' .. initial cap is not valid
publisher: String // ... not 'Publisher'
) extends MediaProperties
object NewspaperProperties {
implicit val format: OFormat[NewspaperProperties] = Json.format[NewspaperProperties]
}
case class PodcastProperties(
x: Int,
y: OffsetDateTime,
z: String
) extends MediaProperties
object PodcastProperties {
implicit val format: OFormat[PodcastProperties] =
Json.format[PodcastProperties]
}
Then a OFormat can be materialized for MediaProperties.
implicit val mediaPropertiesFormat: OFormat[MediaProperties] = Json.format
This managed discriminator in the JSON representation (by default _type field, naming can be configured).
val props1: MediaProperties = PodcastProperties(1, OffsetDateTime.now(), "z")
val obj1 = Json.toJson(props1)
// > JsValue = {"_type":"PodcastProperties","x":1,"y":"2020-11-23T22:53:35.301603+01:00","z":"z"}
obj1.validate[MediaProperties]
JsResult[MediaProperties] = JsSuccess(PodcastProperties(1,2020-11-23T23:02:24.752063+01:00,z),)
The implicit format for MediaProperties should probably be defined in the companion object MediaProperties.
Then the format for Media can be materialized automatically.
final class Id(val value: String) extends AnyVal
object Id {
implicit val format: Format[Id] = Json.valueFormat
}
case class Media(
name: String,
id: Id,
//type: String, -- Not needed for the JSON representation
properties: MediaProperties
)
object Media {
implicit val format: OFormat[Media] = Json.format[Media] // <--- HERE
}

Representing Either in pureconfig

I have a HOCON config like this:
[
{
name = 1
url = "http://example.com"
},
{
name = 2
url = "http://example2.com"
},
{
name = 3
url = {
A = "http://example3.com"
B = "http://example4.com"
}
}
]
I want to parse it with pureconfig.
How can I represent that the URL can be either a string or a map of multiple urls, each having a key?
I have tried this:
import pureconfig.ConfigSource
import pureconfig.generic.auto.exportReader
case class Site(name: Int, url: Either[String, Map[String, String]])
case class Config(sites: List[Site])
ConfigSource.default.loadOrThrow[Config]
But it resulted in "Expected type OBJECT. Found STRING instead."
I know pureconfig supports Option. I have found no mention of supporting Either, does it mean it can be replaced with something else?
As you can see Either in not on list of types supported out of the box.
However Either falls under sealed family, so:
# ConfigSource.string("""{ type: left, value: "test" }""").load[Either[String, String]]
res15: ConfigReader.Result[Either[String, String]] = Right(Left("test"))
# ConfigSource.string("""{ type: right, value: "test" }""").load[Either[String, String]]
res16: ConfigReader.Result[Either[String, String]] = Right(Right("test"))
works. If you have a sealed hierarchy, what pureconfig will do is require an object which has a field type - this field will be used to dispatch parsing to a specific subtype. All the other fields will be passed as fields to parse into that subtype.
If that doesn't work for you, you might try to implement the codec yourself:
// just an example
implicit def eitherReader[A: ConfigReader, B: ConfigReader] =
new ConfigReader[Either[A, B]] {
def from(cur: ConfigCursor) =
// try left, if fail try right
ConfigReader[A].from(cur).map(Left(_)) orElse ConfigReader[B].from(cur).map(Right(_))
}
which now will not require discrimination value:
# ConfigSource.string("""{ test: "test" }""").load[Map[String, Either[String, String]]]
res26: ConfigReader.Result[Map[String, Either[String, String]]] = Right(Map("test" -> Left("test")))
This is not provided by default because you would have to answer a few things yourself:
how do you decide if you should go with Left or Right decoding?
does Left fallback Right or Right fallback Left make sense?
how about Either[X, X]?
If you have an idea what is expected behavior you can implement your own codec and use it in derivation.
There are might be several ways of doing it, but I don't like using Either as a config representation. Thus, I would suggest to use ADT approach with sealed trait:
sealed trait NameUrl {
val name: Int
}
case class Name(
name: Int,
url: String
) extends NameUrl
case class NameUrlObj(
name: Int,
url: Map[String, String]
) extends NameUrl
Sorry for my naming here. This would be a representation of of your config.
We need to modify a bit our config to parse easily the config with you ADT. In order to support generic types you should add your spefici type name for each subtype.
I'm going to put here full example so that you can run it on your machine:
import com.typesafe.config.ConfigFactory
import pureconfig.generic.auto._
import pureconfig.ConfigSource
object TstObj extends App {
sealed trait NameUrl {
val name: Int
}
case class Name(
name: Int,
url: String
) extends NameUrl
case class NameUrlObj(
name: Int,
url: Map[String, String]
) extends NameUrl
val cfgStr = ConfigFactory.parseString(
"""
|abc: [
| {
| type: name,
| name = 1
| url = "http://example.com"
| },
| {
| type: name,
| name = 1
| url = "http://example.com"
| },
| {
| type: name-url-obj,
| name = 3
| url = {
| "A": "http://example3.com"
| "B": "http://example4.com"
| }
| }
|]
|""".stripMargin
)
case class RootA(abc: List[NameUrl])
println(ConfigSource.fromConfig(cfgStr).loadOrThrow[RootA])
}
You can read more here about Sealed Families here

jackson deserialization issue in scala

I have a trait and two case class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
#JsonSubTypes(Array(
new Type(value = classOf[ScreenablePlainTextAddressValue], name = "ScreenablePlainTextAddressValue"),
new Type(value = classOf[ScreenableAddressValue], name = "ScreenableAddressValue")))
trait ScreenableAddress {
def screeningAddressType: String
def addressFormat: String
}
#JsonTypeName(value = "ScreenablePlainTextAddressValue")
case class ScreenablePlainTextAddressValue(addressFormat: String, screeningAddressType: String,
mailingAddress: MailingAddress) extends ScreenableAddress{
//code
}
#JsonTypeName(value = "ScreenableAddressValue")
case class ScreenableAddressValue(addressFormat: String, screeningAddressType: String, value: String)
extends ScreenableAddress{
//code
}
I am trying to deserialize the a json using jackson mapper for the above classes and I get a error as below:
missing property 'type' that is to contain type id (for class com.amazon.isp.execution.model.ScreenableAddress)
My Json look like this:
"{\"schemaVersion\":\"1.0\",\"screeningEntities\":[{\"screeningRecordList\":[{\"address\":{}},{\"address\":{\"addressFormat\":\"ADDRESS_ID\",\"value\":\"SK36SAV4WD4BSGDLGZQEG05RMMA0261533395I3IZ4AMMRVPXTQ2EIA2OXZFEHLF\",\"screeningAddressType\":\"OTHER_ADDRESS\"}},{\"name\":{}},{\"name\":{\"screeningNameType\":\"OTHER_NAME\",\"nameFormat\":\"KEYMASTER_SEALED_FULL_NAME\",\"value\":\"AAAAAAAAAADYzV1Zr7RxcB5y53pylNH4KgAAAAAAAAD3vya/1QtdPxVBZM+alrMIZZvaz9DHm+w+qby7z/c8YutbxeoX6so+mGY=\"}},{\"name\":{\"screeningNameType\":\"LEGAL_NAME\",\"nameFormat\":\"KEYMASTER_SEALED_FULL_NAME\",\"value\":\"AAAAAAAAAADYzV1Zr7RxcB5y53pylNH4KgAAAAAAAACRUkz7uKJGOQCB0pZWXRXTFrk9Enpucj33hV+/yHRM/dQKo2yWGxGcjB8=\"}}]}],\"screeningMetaData\":{\"customerId\":\"" + CustomerID + "\"}}"
Any help will be really appreciated.

JSON4S does not serialize internal case class members

I have a case class inheriting from a trait:
trait Thing {
val name: String
val created: DateTime = DateTime.now
}
case class Door(override val name: String) extends Thing
This is akka-http, and I'm trying to return JSON to a get request:
...
~
path ("get" / Segment) { id =>
get {
onComplete(doorsManager ? ThingsManager.Get(id)) {
case Success(d: Door) => {
complete(200, d)
}
case Success(_) => {
complete(404, s"door $id not found")
}
case Failure(reason) => complete(500, reason)
}
}
} ~
...
but I only get the JSON of name. I do have the implicit Joda serializers in scope.
if i override the 'created' timestamp in the constructor of the case class, it does get serialized, but it defines the purpose, as I don't need (or want) the user to provide the timestamp. I've tried moving the timestamp into Door (either as override or just by skipping the trait) and the result is the same (that is, no 'created').
how do I tell JSON4S to serialize internal members (and inherited ones) too?
You have to define a custom format.
import org.json4s.{FieldSerializer, DefaultFormats}
import org.json4s.native.Serialization.write
case class Door(override val name: String) extends Thing
trait Thing {
val name: String
val created: DateTime = DateTime.now
}
implicit val formats = DefaultFormats + FieldSerializer[Door with Thing()]
val obj = new Door("dooor")
write(obj)

JSON serialization of Scala enums using Jackson

Following this article https://github.com/FasterXML/jackson-module-scala/wiki/Enumerations
The enumeration declaration is as
object UserStatus extends Enumeration {
type UserStatus = Value
val Active, Paused = Value
}
class UserStatusType extends TypeReference[UserStatus.type]
case class UserStatusHolder(#JsonScalaEnumeration(classOf[UserStatusType]) enum: UserStatus.UserStatus)
The DTO is declared as
class UserInfo(val emailAddress: String, val userStatus:UserStatusHolder) {
}
and the serialization code is
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
def serialize(value: Any): String = {
import java.io.StringWriter
val writer = new StringWriter()
mapper.writeValue(writer, value)
writer.toString
}
The resulting JSON serialization is
{
"emailAddress":"user1#test.com",
"userStatus":{"enum":"Active"}
}
Is it possible to get it the following form ?
{
"emailAddress":"user1#test.com",
"userStatus":"Active"
}
Have you tried:
case class UserInfo(
emailAddress: String,
#JsonScalaEnumeration(classOf[UserStatusType]) userStatus: UserStatus.UserStatus
)
The jackson wiki's example is a little misleading. You don't need the holder class. Its just an example of a thing that has that element. The thing you need is the annotation