Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Argonaut to create generic JSON converter

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.

like image 864
Wins Avatar asked Feb 15 '23 05:02

Wins


1 Answers

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].

like image 71
Ben James Avatar answered Feb 28 '23 09:02

Ben James