将分隔字符串反序列化为 case 类的惯用 Scala 方法 [英] Idiomatic Scala way of deserializing delimited strings into case classes

查看:57
本文介绍了将分隔字符串反序列化为 case 类的惯用 Scala 方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我正在处理一个简单的冒号分隔的文本协议,它看起来像:

Suppose I was dealing with a simple colon-delimited text protocol that looked something like:

Event:005003:information:2013 12 06 12 37 55:n3.swmml20861:1:Full client swmml20861 registered [entry=280 PID=20864 queue=0x4ca9001b]
RSET:m3node:AUTRS:1-1-24:A:0:LOADSHARE:INHIBITED:0
M3UA_IP_LINK:m3node:AUT001LKSET1:AUT001LK1:r
OPC:m3node:1-10-2(P):A7:NAT0
....

我想将每一行反序列化为 case 类的一个实例,但以类型安全的方式.我的第一次尝试使用类型类为我可能遇到的每种可能的类型定义读取"方法,此外还使用案例类上的元组"方法来获取可应用于参数元组的函数,例如以下内容:

I'd like to deserialize each line as an instance of a case class, but in a type-safe way. My first attempt uses type classes to define 'read' methods for each possible type that I can encounter, in addition to the 'tupled' method on the case class to get back a function that can be applied to a tuple of arguments, something like the following:

case class Foo(a: String, b: Integer)

trait Reader[T] {
  def read(s: String): T
}

object Reader {
  implicit object StringParser extends Reader[String] { def read(s: String): String = s }
  implicit object IntParser extends Reader[Integer] { def read(s: String): Integer = s.toInt }
}

def create[A1, A2, Ret](fs: Seq[String], f: ((A1, A2)) => Ret)(implicit A1Reader: Reader[A1], A2Reader: Reader[A2]): Ret = {
  f((A1Reader.read(fs(0)), A2Reader.read(fs(1))))
}

create(Seq("foo", "42"), Foo.tupled) // gives me a Foo("foo", 42)

问题是我需要为每个元组和函数参数定义 create 方法,所以这意味着最多 22 个版本的 create.此外,这不会处理验证或接收损坏的数据.

The problem though is that I'd need to define the create method for each tuple and function arity, so that means up to 22 versions of create. Additionally, this doesn't take care of validation, or receiving corrupt data.

推荐答案

因为有一个 Shapeless 标签,使用它的一个可能的解决方案,但我不是专家,我想有人可以做到更好:

As there is a Shapeless tag, a possible solution using it, but I'm not an expert and I guess one can do better :

首先,关于缺乏验证,如果您不关心错误消息,您应该简单地阅读 return Try,或 scalaz.Validation 或只是 option.

First, about the lack of validation, you should simply have read return Try, or scalaz.Validation or just option if you do not care about an error message.

那么关于样板文件,你可以尝试使用 HList.这样你就不需要去寻找所有的 arities.

Then about boilerplate, you may try to use HList. This way you don't need to go for all the arities.

import scala.util._
import shapeless._

trait Reader[+A] { self =>
  def read(s: String) : Try[A]
  def map[B](f: A => B): Reader[B] = new Reader[B] {
    def read(s: String) = self.read(s).map(f)
  }
}    

object Reader {
  // convenience
  def apply[A: Reader] : Reader[A] = implicitly[Reader[A]]
  def read[A: Reader](s: String): Try[A] = implicitly[Reader[A]].read(s)

  // base types
  implicit object StringReader extends Reader[String] {
    def read(s: String) = Success(s)
  }
  implicit object IntReader extends Reader[Int] {
    def read(s: String) = Try {s.toInt}
  }

  // HLists, parts separated by ":"
  implicit object HNilReader extends Reader[HNil] {
    def read(s: String) = 
      if (s.isEmpty()) Success(HNil) 
      else Failure(new Exception("Expect empty"))
  }
  implicit def HListReader[A : Reader, H <: HList : Reader] : Reader[A :: H] 
  = new Reader[A :: H] {
    def read(s: String) = {
      val (before, colonAndBeyond) = s.span(_ != ':')
      val after = if (colonAndBeyond.isEmpty()) "" else colonAndBeyond.tail
      for {
        a <- Reader.read[A](before)
        b <- Reader.read[H](after)
      } yield a :: b
    }
  }

}

鉴于此,您对 Foo 的阅读量相当短:

Given that, you have a reasonably short reader for Foo :

case class Foo(a: Int, s: String) 

object Foo {
  implicit val FooReader : Reader[Foo] = 
    Reader[Int :: String :: HNil].map(Generic[Foo].from _)
}

它有效:

println(Reader.read[Foo]("12:text"))
Success(Foo(12,text))

这篇关于将分隔字符串反序列化为 case 类的惯用 Scala 方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆