5

I have some working jackson scala module code for roundtripping scala case classes. Jackson worked great for flat case classes but when I made one which contains a list of other case classes the amount of code I seemed to need was a lot. Consider:

abstract class Message
case class CardDrawn(player: Long, card: Int, mType: String = "CardDrawn") extends Message
case class CardSet(cards: List[CardDrawn], mType: String = "CardSet") extends Message

To get the CardSet to roundtrip to/from json with jackson scala module I used a custom serializer/deserializer written in java:

object ScrumGameMashaller {

  val mapper = new ObjectMapper() 
  val module = new SimpleModule("CustomSerializer")
  module.addSerializer(classOf[CardSet], new CardSetSerializer)
  module.addDeserializer(classOf[CardSet], new CardSetDeserializer)
  val scalaModule = DefaultScalaModule
  mapper.registerModule(scalaModule)
  mapper.registerModule(module)

  def jsonFrom(value: Any): String = {
    import java.io.StringWriter
    val writer = new StringWriter()
    mapper.writeValue(writer, value)
    writer.toString
  }

  private[this] def objectFrom[T: Manifest](value: String): T =
    mapper.readValue(value, typeReference[T])

  private[this] def typeReference[T: Manifest] = new TypeReference[T] {
    override def getType = typeFromManifest(manifest[T])
  }

  private[this] def typeFromManifest(m: Manifest[_]): Type = {
    if (m.typeArguments.isEmpty) { m.runtimeClass }
    else new ParameterizedType {
      def getRawType = m.runtimeClass
      def getActualTypeArguments = m.typeArguments.map(typeFromManifest).toArray
      def getOwnerType = null
    }
  }

with serializer:

public class CardSetSerializer extends JsonSerializer<CardSet> {
@Override
    public void serialize(CardSet cardSet, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeArrayFieldStart("cards");
        List<CardDrawn> cardsDrawn = cardSet.cards();
        scala.collection.Iterator<CardDrawn> iter = cardsDrawn.iterator();
        while(iter.hasNext()){
            CardDrawn cd = iter.next();
            cdSerialize(jgen,cd);
        }
        jgen.writeEndArray();
        jgen.writeStringField("mType", "CardSet");
        jgen.writeEndObject();      
    }

    private void cdSerialize(JsonGenerator jgen, CardDrawn cd) throws IOException, JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("player", cd.player());
        jgen.writeNumberField("card", cd.card());
        jgen.writeEndObject();
    }
}

and matching deserializer:

public class CardSetDeserializer extends JsonDeserializer<CardSet> {

    private static class CardDrawnTuple {
        Long player;
        Integer card;
    }

    @Override
    public CardSet deserialize(JsonParser jsonParser, DeserializationContext cxt) throws IOException, JsonProcessingException {
        ObjectCodec oc = jsonParser.getCodec();
        JsonNode root = oc.readTree(jsonParser);
        JsonNode cards = root.get("cards");
        Iterator<JsonNode> i = cards.elements();
        List<CardDrawn> cardObjects = new ArrayList<>();
        while( i.hasNext() ){
            CardDrawnTuple t = new CardDrawnTuple();
            ObjectNode c = (ObjectNode) i.next();
            Iterator<Entry<String, JsonNode>> fields = c.fields();
            while( fields.hasNext() ){
                Entry<String,JsonNode> f = fields.next();
                if( f.getKey().equals("player")) {
                    t.player = f.getValue().asLong();
                } else if( f.getKey().equals("card")){
                    t.card = f.getValue().asInt();
                } else { 
                    System.err.println(CardSetDeserializer.class.getCanonicalName()+ " : unknown field " + f.getKey());
                }
            }
            CardDrawn cd = new CardDrawn(t.player, t.card, "CardDrawn");
            cardObjects.add(cd);
        }

        return new CardSet(JavaConversions.asScalaBuffer(cardObjects).toList(), "CardSet");
    }

}

This seems like a lot code to deal with something fairly vanilla in the scala. Can this code be improved (what did I miss that jackson has to make this easy)? Else is there a library which will do structured case classes automatically? The jerkson examples looked easy but that seems to have been abandoned.

3
  • I tried jacks which looked promising but had an issue with these classes which I reported here github.com/wg/jacks/issues/15 Commented Sep 4, 2013 at 19:54
  • Argonaut does the job with just implicit lazy val CodecCardSet: CodecJson[CardSet] = casecodec2(CardSet.apply, CardSet.unapply)("cards","mType") and implicit lazy val CodecCardDrawn: CodecJson[CardDrawn] = casecodec3(CardDrawn.apply, CardDrawn.unapply)("player", "card", "mType") see example at github.com/argonaut-io/argonaut/issues/64 Commented Sep 5, 2013 at 6:02
  • have you considered using the Scala Jackson Module? github.com/FasterXML/jackson-module-scala Commented Feb 23, 2016 at 2:32

2 Answers 2

0

Argonaut does a great job. Mark Hibbard helped me out with getting the example below working. All that is needed is to create a codec for the types and it will implicitly add an asJson to your objects to turn them into strings. It will also add a decodeOption[YourClass] to strings to extract an object. The following:

package argonaut.example

import argonaut._, Argonaut._

abstract class Message
case class CardDrawn(player: Long, card: Int, mType: String = "CardDrawn") extends Message
case class CardSet(cards: List[CardDrawn], mType: String = "CardSet") extends Message

object CardSetExample  {

  implicit lazy val CodecCardSet: CodecJson[CardSet] = casecodec2(CardSet.apply, CardSet.unapply)("cards","mType")
  implicit lazy val CodecCardDrawn: CodecJson[CardDrawn] = casecodec3(CardDrawn.apply, CardDrawn.unapply)("player", "card", "mType")

  def main(args: Array[String]): Unit = {
    val value = CardSet(List(CardDrawn(1L,2),CardDrawn(3L,4)))
    println(s"Got some good json ${value.asJson}")

    val jstring =
      """{
        | "cards":[
        |   {"player":"1","card":2,"mType":"CardDrawn"},
        |   {"player":"3","card":4,"mType":"CardDrawn"}
        | ],
        | "mType":"CardSet"
        | }""".stripMargin

    val parsed: Option[CardSet] =
      jstring.decodeOption[CardSet]

    println(s"Got a good object ${parsed.get}")
  }
}

outputs:

Got some good json {"cards":[{"player":"1","card":2,"mType":"CardDrawn"},{"player":"3","card":4,"mType":"CardDrawn"}],"mType":"CardSet"}

Got a good object CardSet(List(CardDrawn(1,2,CardDrawn), CardDrawn(3,4,CardDrawn)),CardSet)

Sign up to request clarification or add additional context in comments.

Comments

0

The question is old but maybe someone could still find it helpful. Apart from Argonaut, Scala has several Json libraries. Here you can find a list of them updated to the beginning of 2016 (and it still gives you a good overall picture). Most of them (probably all) should allow you to come up with a drier version of your custom serializer/deserailizer. My preference goes to json4s which aims to provide a single AST across multiple libraries including Jackson (a bit like slf4j does for logging libraries). In this post you can find a working example of a Json custom serializer/deserializer using Json4s and Akka Http.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.