我有一個trait這是某些案例類的基類:
trait BaseHeaders {
val timestamp: LocalDateTime
val host: String
val os: String
val method: String
val userAgent: String
}
object BaseHeaders {
implicit val traitWrites = new Writes[BaseHeaders] {
def writes(baseHeaders: BaseHeaders) = JsString(BaseHeaders.toString)
}
}
我的一個案例類類似于以下一個:
case class PaymentHeader(
timestamp: LocalDateTime,
host: String,
os: String,
method: String,
path: String,
userAgent: String,
paymentStatus: String,
) extends BaseHeaders {
val kafkaEventPublisher = new KafkaEventPublisher("payment-topic")
def publish(): Unit = kafkaEventPublisher.publishLog(this)
}
在我companion object構建所需的案例類:
object PaymentFunnel {
def from(req: HttpRequest, status: String): PaymentHeader = {
val (host, userAgent, channel, os) = HeaderExtractor.getHeaders(req)
PaymentHeader(
LocalDateTime.now(),
host,
os,
req.method.value,
req.uri.path.toString,
userAgent,
channel,
status,
)
}
implicit val format: Format[PaymentHeader] = JsonNaming.snakecase(Json.format[PaymentHeader])
}
現在我的最終方法publishLog定義如下:
def publishLog(message: BaseHeaders) = {
Source.fromIterator(() => List(message).iterator)
.map(baseHeader => {
println("publishing headers data to kafka ::::: " Json.toJson(baseHeader).toString())
new ProducerRecord[String, String](topicName, Json.toJson(baseHeader).toString())
})
.runWith(Producer.plainSink(producerSettings))
}
現在我在 Kafka 主題上得到的是一個字串版本case class:
`PaymentHeader(some data here for fields....)`
案例類的字串版本!我在 Kafka 端想要的是序列化 json PaymentHeader。
注意:我有許多其他案例類擴展BaseHeaders為PaymentHeaders.
如何在 Scala/Play 中對我的案例類進行 json 編碼?
uj5u.com熱心網友回復:
您當前正在執行的操作是呼叫toString基本上回傳字串表示 obj Scala 中的物件的方法,并將其JsString假定為字串,并在其周圍加上雙引號。我可以提出 3 種方法,我將解釋每種方法的優缺點,您可以決定使用哪一種:
方法#1:進行模式匹配
當序列化一個特征時,在這種情況下重要的是子型別,所以想象一下:
sealed trait A { val name: String }
case class B(name: String) extends A
case class C(name: String, id: Int) extends A
/// and so on
您可以為這些類中的每一個定義 writer,然后在 trait 的伴生物件中:
object A {
implicit val traitWriter: Writes[A] = {
case b: B => Json.toJson(b)
case c: C => Json.toJson(c)
}
}
這種方法很容易使用,但需要注意的是,當創建一個擴展 trait 的新類時,您需要更新 trait 的伴生物件中的 writer,否則您將面臨匹配錯誤。這樣做的好處是您的代碼非常簡單易讀。缺點是你無法確定編譯時的行程(匹配錯誤問題)
方法#2:消費者方法中的型別約束和背景關系系結
在publishLog方法中,將方法簽名更改為:
// original:
def publishLog(message: BaseHeaders) = ...
// to =>
def publishLog[T <: BaseHeaders : Writes](message: T) = ... // the body will be the same
只需從 trait 的伴生物件中洗掉 writer。這意味著,您是否期望某種型別的“T”實際上是 a BaseHeaders,并且也有它自己的Writer系結,因此不會出現序列化問題。這種方法的優點是,(1)您的代碼仍然簡單易讀,(2)您將擺脫定義在所有情況下都適用于該特征的撰寫器的麻煩,(3)您'確定編譯時的一切。這種方法的缺點是,每當您想使用一個BaseHeaders值時,您都必須為您的方法定義相同的簽名(如果您需要序列化),但這對我來說似乎是公平的。
方法 #3:使用 self 型別放置實體級約束(不推薦)
trait BaseHeaders[T <: BaseHeaders[T]] { self: T =>
val timestamp: LocalDateTime
// ... other fields
implicit def instanceWriter: Writes[T]
}
case class PaymentHeader(...) extends BaseHeaders[PaymentHeader] {
implicit def instanceWriter: Writes[PaymentHeader] = implicitly
}
這樣做的一個好處是您可以確定編譯時的序列化雖然它有一個主要缺點,但代碼實際上變得比它應該的要難得多,這根本不好。另一個是您需要instanceWriter在需要序列化時呼叫。
uj5u.com熱心網友回復:
Json.toJson根據引數的編譯時型別而不是運行時型別轉換類,因此它將其轉換為BaseHeaders物件。的撰寫器BaseHeaders只是將 轉換case class為字串,這就是您在 JSON 中得到的。
您需要對該publishLog方法進行模板化,以便Json.toJson查看訊息的實際型別。
def publishLog[T <: BaseHeaders](message: T)
您還需要確保訊息型別保留在呼叫函式中,并且 Play 知道如何將實際型別轉換為 JSON。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/480333.html
