I'm new to Scala, and here I'm trying to create a generic json converter based on Argonaut. I've tried to search on google and stackoverflow, but so far I have no clue.
Here is the snippet of my code.
import org.springframework.http.converter.AbstractHttpMessageConverter
import org.springframework.http.{MediaType, HttpInputMessage, HttpOutputMessage}
import scala.io.Source
import argonaut._,Argonaut._
case class Currency(code: String)
object Currency {
implicit def CurrencyCodecJson: CodecJson[Currency] = casecodec1(Currency.apply, Currency.unapply)("code")
}
case class Person(firstName: String, lastName: String)
object Person {
implicit def PersonCodecJson: CodecJson[Person] = casecodec2(Person.apply, Person.unapply)("firstName", "LastName")
}
class ArgonautConverter extends AbstractHttpMessageConverter[Object](new MediaType("application", "json", Charset.forName("UTF-8")), new MediaType("application", "*+json", Charset.forName("UTF-8"))) {
val c = classOf[Currency]
val p = classOf[Person]
def writeInternal(t: Object, outputStream: OutputStream) = {
val jsonString = t match {
case c:Currency => c.asJson.ToString()
case p:Person => p.asJson.ToString()
}
def supports(clazz: Class[_]): Boolean = clazz.isAssignableFrom(classOf[CodecJson])// clazz == classOf[Currency] || clazz == classOf[LegalEntity]
def readInternal(clazz: Class[_ <: Object], inputStream: InputStream): Object = {
val jsonString = Source.fromInputStream(inputStream).getLines.mkString
val jsonObject = clazz match {
case `c` => jsonString.decodeOption[Currency]
case `p` => jsonString.decodeOption[Person]
}
jsonObject match {
case Some(j) => j
case None => null
}
}
}
What I'm trying to do is to generalize such that I don't need to keep adding the match for every new model class (like Currency and Person in this case) that I will add in the future.
Argonaut already has generic encoding and decoding functions.
For example, Parse.decodeOption
will parse a String into any type for which you have a codec.
What you are trying to do is decide at runtime whether you have a codec for a type, but you can make the compiler figure that out for you.
Whether you can decode to a type T
depends on whether there is an implicit instance of DecodeJson[T]
in scope. (That is a supertype of CodecJson[T]
, and you have written a couple of those, so they're good.)
Unfortunately, the compiler won't infer this constraint for you, so you have to mention it in the type signature. One way to do this is to use a context bound, which is the T : DecodeJson
in the example below.
def read[T : DecodeJson](inputStream: InputStream): Option[T] = {
val jsonString = Source.fromInputStream(inputStream).getLines.mkString
Parse.decodeOption(jsonString)
}
(Also, note that you should really return Option[T]
instead of using null
.)
Similarly, write
could be implemented with the signature:
def write[T : EncodeJSON](t: T, outputStream: OutputStream)
Your CodecJson[T]
instances are also instances of EncodeJson[T]
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With