【问题标题】:Custom json serialization of structured scala case classes结构化scala案例类的自定义json序列化
【发布时间】:2013-09-04 21:10:22
【问题描述】:

我有一些用于往返 scala 案例类的可用 jackson scala 模块代码。 Jackson 非常适合扁平案例类,但是当我制作一个包含其他案例类列表的案例时,我似乎需要的代码量很多。考虑:

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

为了让 CardSet 通过 jackson scala 模块往返于 json 之间,我使用了一个用 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
    }
  }

带序列化器:

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();
    }
}

和匹配的反序列化器:

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");
    }

}

这似乎有很多代码来处理 scala 中相当普通的事情。这段代码可以改进吗(我错过了杰克逊必须让这件事变得简单的什么)?否则是否有一个库可以自动执行结构化案例类? jerkson 示例看起来很简单,但似乎已被放弃。

【问题讨论】:

  • 我尝试了看起来很有希望的千斤顶,但我在此处报告了这些类的问题github.com/wg/jacks/issues/15
  • Argonaut 仅使用 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") 完成这项工作,请参见 github.com/argonaut-io/argonaut/issues/64 的示例
  • 您考虑过使用 Scala Jackson 模块吗? github.com/FasterXML/jackson-module-scala

标签: scala jackson jerkson


【解决方案1】:

这个问题很老,但也许有人仍然会觉得它有帮助。除了 Argonaut,Scala 还有几个 Json 库。 Here 您可以找到更新到 2016 年初的列表(它仍然可以为您提供良好的整体情况)。 它们中的大多数(可能全部)应该允许您提出自定义序列化器/解轨器的更干燥版本。我更喜欢json4s,它旨在为包括 Jackson 在内的多个库提供单个 AST(有点像 slf4j 为日志库所做的那样)。 In this post 你可以找到一个使用 Json4s 和 Akka Http 的 Json 自定义序列化器/反序列化器的工作示例。

【讨论】:

    【解决方案2】:

    Argonaut 做得很好。 Mark Hibbard 帮助我完成了下面的示例。所需要的只是为这些类型创建一个编解码器,它会隐式地将asJson 添加到您的对象中以将它们转换为字符串。它还将在字符串中添加decodeOption[YourClass] 以提取对象。以下:

    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}")
      }
    }
    

    输出:

    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)

    【讨论】:

      猜你喜欢
      • 2017-03-29
      • 2011-11-08
      • 2013-08-04
      • 1970-01-01
      • 2013-08-12
      • 2020-04-03
      • 2014-08-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多