帮酷LOGO
0 0 评论
  • 显示原文与译文双语对照的内容
文章标签:Extensible  Shape  序列化  Shapeless  ext  BASE  SHA  
A simple yet extensible shapeless-based serialization library

  • 源代码名称:picopickle
  • 源代码网址:http://www.github.com/netvl/picopickle
  • picopickle源代码文档
  • picopickle源代码下载
  • Git URL:
    git://www.github.com/netvl/picopickle.git
  • Git Clone代码到本地:
    git clone http://www.github.com/netvl/picopickle
  • Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/netvl/picopickle
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
  • picopickle-0.3.2

    picopickle是 Scala的序列化库。 它的主要特点是:

    • 小的,几乎依赖的( 核心库只依赖于没有形状的 。)。
    • Extensibility: 你可以为你的类型定义自己的serializators,也可以创建自定义的后端,即,你可以为不同的序列化格式( 如空处理) 使用相同的库。
    • 灵活性和方便性:默认的序列化格式适用于大多数用途,但是可以通过方便的转换器DSL定制。
    • 不带反射的static 序列化:无形状的Generic 用于提供任意类型的序列化程序,这意味着不使用反射。

    电子邮件内容

    开始启动

    库发布到 Maven 中心,因此你可以只向 build.sbt 文件添加以下行,以便使用核心库:

    libraryDependencies +="io.github.netvl.picopickle"%%"picopickle-core"%"0.3.2"

    库是为 Scala 版本 2.10,2.11,2.12编译的。 不过,如果使用 2.10,则需要添加宏 Paradise 编译器插件,因为不适合的MACROS 依赖于它:

    libraryDependencies += compilerPlugin("org.scalamacros"%%"paradise"%"2.0.1" cross CrossVersion.full)// oraddCompilerPlugin("org.scalamacros"%%"paradise"%"2.0.1" cross CrossVersion.full)

    Scala 2.11/12 用户不需要这个,因为所有相关的宏支持已经存在于 2.11/12. 中

    后端依赖项

    Picopickle支持不同的后端。 后端定义目标序列化格式,例如 JSON。BSON或者常规集合。 核心库提供集合后端,并且基于 Jawn 解析器的附加JSON后端可以作为 picopickle-backend-jawn 提供:

    libraryDependencies +="io.github.netvl.picopickle"%%"picopickle-backend-jawn"%"0.3.2"

    Jawn后端使用Jawn解析器( 自然的) 读取JSON字符串,但它使用自定义渲染器作为字符串来保持依赖。 这个渲染器非常基本,并且不支持打印;这是可以能在未来版本中修复的。

    你可以创建自己的后端来支持自己的数据格式;更多关于如何使用它的信息在下面提供。 以后可能会有更正式的后台支持。

    序列化机制

    picopickle使用非常标准的typeclass方法,其中类型的序列化或者反序列化是通过范围内的隐式对象( 在picopickle中调用 Reader[T]Writer[T] ) 定义的。 库为许多标准类型定义相应的实例:

    • 基本类型和其他基本类型: ByteShortIntLongFloatDoubleBooleanCharStringUnit,,gb2312 ;
    • 元组( 当前生成为生成过程的一部分,长度从 1到 22 ) ;
    • 大多数标准 Scala 集合;
    • 密封特征层次:案例类和案例对象,可能实现某些密封特性,以及密封特性本身。

    在不变形的LabelledGeneric 类型类的帮助下,自动派生密封trait层次的序列化程序。 图书馆定义了核心无形类型( HListCoproduct )的几个通用实例,形状不形式和密封特性。

    由于密封特征层次结构等价于代数数据类型,因此它们与无形状类型的表示是相当自然的: 每个案例类/对象都用字段名称的HList 表示,整个层次结构由实现密封特性的Coproduct 表示。

    picopickle也支持递归类型,也就是说,当一个事例类最终依赖于自身或者它属于的密封特性时,例如:

    sealedtraitRootcaseobjectAextendsRootcaseclassB(x: Int, b: Option[B]) extendsRoot// depends on itselfcaseclassC(next: Root) extendsRoot// depends on the sealed trait

    picopickle还支持默认值和变量参数,以及用一些自定义宏重命名字段或者密封特性后代。

    用法

    基本用法

    使用 Pattern 构造 picopickle,即它包含几个功能,提供一些功能,然后将它的组合成一个单独的对象,即一个名为的pickler。 它提供了通过全局导入进行序列化所必需的一切:

    importsome.package.SomePickler._write("Hello") shouldEqual SomePicklerBackend.StringValue("Hello")

    core库和Jawn后端库提供默认 picklers,因此如果你不需要任何定制( 比如 )。 你不需要为类型定义自定义序列化程序,只需导入它的中一个picklers的内部内容:

    importio.github.netvl.picopickle.backends.collections.CollectionsPickler._caseclassA(x: Int, y: String)
    write(A(10, "hi")) shouldEqual Map("x"->10, "y"->"hi")
    read[A](Map("x"->10, "y"->"hi")) shouldEqual A(10, "hi")

    基于jawn的pickler还提供附加函数 readString()/writeString()readAst()/writeAst(),[de] 将对象分别序列化为字符串和 JSON AST到字符串:

    importio.github.netvl.picopickle.backends.jawn.JsonPickler._caseclassA(x: Int, y: String)
    writeString(A(10, "hi")) shouldEqual """{"x":10,"y":"hi"}"""readString[A]("""{"x":10,"y":"hi"}""") shouldEqual A(10, "hi")

    当前,字符串JSON表示不是 prettified ( 但是prettification可以在以后的版本中实现)。

    序列化程序对象

    有时你需要在同一段代码中使用序列化和 deserializaton ( 比如。 从数据库中写入和读取数据。 为了正确起见,为了在一个地方实例化相应的读者和作者,可以方便地为某些特定的类型修复 readwrite 方法。

    picopickle提供了一个特殊的序列化类,可以为任何具有 ReaderWriter 实例的类型构造。 此类提供为创建这里序列化程序的类型指定的readwrite 方法:

    importio.github.netvl.picopickle.backends.collections.CollectionsPickler._caseclassA(x: Int, y: String)valaSerializer= serializer[A]
    aSerializer.write(A(10, "hi")) shouldEqual Map("x"->10, "y"->"hi")// aSerializer.write("whatever")//won't compile - write() accepts values of type A onlyaSerializer.read(Map("x"->10, "y"->"hi") shouldEqual A(10, "hi")// val x: String = aSerializer.read("whatever")//won't compile - read() returns values of type A

    基于jawn的pickler扩展了此类,以提供 readStringwriteString 方法:

    importio.github.netvl.picopickle.backends.jawn.JsonPickler._caseclassA(x: Int, y: String)valaSerializer= serializer[A]
    aSerializer.writeString(A(10, "hi")) shouldEqual """{"x":10,"y":"hi"}"""aSerializer.readString("""{"x":10,"y":"hi"}""") shouldEqual A(10, "hi")

    Custom Custom

    你可能希望为某些类型定义自定义序列化程序。 这样,你可以在"依赖"上定义自定义序列化程序实例,并通过自类型注释在 BackendComponent 上和 TypesComponent 上定义该实例:

    importio.github.netvl.picopickle.{BackendComponent, TypesComponent}caseclassDefinedByInt(x: Int, y: String)traitCustomSerializers {
     this:BackendComponentwithTypesComponent=>implicitvaldefinedByIntWriter:Writer[DefinedByInt] =Writer {
     caseDefinedByInt(x, _) => backend.makeNumber(x)
     }
     implicitvaldefinedByIntReader:Reader[DefinedByInt] =Reader {
     case backend.Extract.Number(x) =>DefinedByInt(x.intValue(), x.intValue().toString)
     }
    }

    然后,应该将这里特性与库中定义的对应的pickler特性混合,以便创建pickler对象:

    importio.github.netvl.picopickle.backends.jawn.JsonPicklerobjectCustomPicklerextendsJsonPicklerwithCustomSerializers

    如果不应该重用或者只有程序中的一个pickler对象,也可以直接在pickler对象中定义序列化程序:

    importio.github.netvl.picopickle.backends.jawn.JsonPicklerobjectCustomPicklerextendsJsonPickler {
     implicitvaldefinedByIntWriter:Writer[DefinedByInt] =Writer {
     caseDefinedByInt(x, _) => backend.makeNumber(x)
     }
     implicitvaldefinedByIntReader:Reader[DefinedByInt] =Reader {
     case backend.Extract.Number(x) =>DefinedByInt(x.intValue(), x.intValue().toString)
     }
    }

    另外,你可以导入某些pickler内部组件并在任何位置定义序列化程序,但是你需要为在其中定义这些序列化程序的任何位置添加导入:

    object CustomPickler extends JsonPickler
    object CustomSerializers {
     import CustomPickler._
     implicit val definedByIntWriter: Writer[DefinedByInt] = Writer {
     case DefinedByInt(x, _) => backend.makeNumber(x)
     }
     implicit val definedByIntReader: Reader[DefinedByInt] = Reader {
     case backend.Extract.Number(x) => DefinedByInt(x.intValue(), x.intValue().toString)
     }
    }
    import CustomSerializers._
    CustomPickler.write(DefinedByInt(10,"10")) shouldEqual"""10"""

    这种方法也禁止你对程序中的不同类型的picklers使用相同的序列化程序。

    picopickle提供了几个帮助你编写自定义序列化程序和反序列化程序的工具;但是,首先,我们需要解释后端是什么。

    后端

    在picopickle中,一个基于xml的后端定义了一个称为后端表示的中间 AST,它是其他类型的值,可以被序列化为其他类型的media。 例如对于 JSON,它是 JSON AST,即一组可以用来构成任何正确的JSON对象树的类。 另外,后端提供了一些方法来从基本的Scala 类型和集合构造这些值,并将这些值分解成这些基本类型。

    一般来说,后端可能是任意复杂的。 它由许多类组成,它们之间有各种关系,以及构造它们的所有必要方法。 但是,为了提供任意类型到任意后端表示的能力,应该对后端表示的结构进行一些限制。 picopickle要求所有后端都支持基本的json类型,即由字符串键入的对象。数组。数字。数字。 使用这些原语,picopickle可以为基本的基本类型和密封的特性层次结构提供序列化程序。

    Backend 特性用于表示 Scala 代码中的后端。 这个特性包含抽象类型,定义AST和许多方法,从基本类型构造 AST。 这里特性的每个实现都应提供以下抽象类型:

    typeBValuetypeBObject<:BValuetypeBArray<:BValuetypeBString<:BValuetypeBNumber<:BValuetypeBBoolean<:BValuetypeBNull<:BValue

    另外每个实现必须提供一组方法来在这些抽象类型和基本 Scala 类型之间进行转换。 映射如下所示:

    BObject -> Map[String, BValue]
    BArray -> Vector[BValue]
    BString -> String
    BNumber -> Number
    BBoolean -> Boolean
    BNull -> Null

    也就是说,每个后端应该提供从 BValue 转换为 Map[String, BValue] 和 等等的方法,这些方法可以分为三个组:

    • 将 Scala 值转换为后端表示形式的步骤: 带 make 前缀
    • 将后端表示转换为 Scala 值的步骤: 带 from 前缀
    • 提取具体后端类型( 比如。 来自抽象 BValueBObjectBString: 带 get 前缀。

    最后一组方法返回 Option[<corresponding type>],因为它们在本质上是部分。

    还可以通过与相应的from 方法进行转换定义 makeEmptyObject 或者 getArrayValueAt 之类的一些便捷方法,然后直接查询结果 Scala 对象,但这些方法可以直接查询底层的后端对象,而不是直接查询中间对象。

    要创建自定义后端,你需要先实现 Backend 特性:

    objectMyBackendextendsBackend {
     typeBValue=.. .
    . . .
    }

    然后需要为此后端创建一个 Cake 组件;该组件必须实现 BackendComponent 特性:

    traitMyBackendComponentextendsBackendComponent {
     overridevalbackend=MyBackend}

    最后,你应该扩展 DefaultPickler,将它的与后端组件混合:

    traitMyPicklerextendsDefaultPicklerwithMyBackendComponentobjectMyPicklerextendsMyPickler

    自然,如果不想将 DefaultPickler 完全合并到pickler中,如果不需要自动编写密封特性层次结构。 在这种情况下,你只能混合那些你需要的特性。 查看 DefaultPickler 文档以查找它由( 待办事项) 组成的组件。

    MyPickler.readMyPickler.write 方法处理后端表示之后。

    扩展后端

    有时,默认 Backend 特性提供的类型和方法集是不够的,因为所需的目标表示支持更多类型和方法。 一个例子是,除了所有标准JSON类型,还有日期和时间。对象,。显式 32位 和 64位 整数和字节数组。 自然,人们希望将 Scala 类型自动序列化为可用的最有效的表示形式。

    在这方面picopickle是可以扩展的。 因为后端只是一个特性的实现,所以无法将新的具体类型添加到后端实现中。

    // Define a new backendobjectBsonBackendextendsBackend {
     // implement required typesoverridetypeBValue=BsonValueoverridetypeBObject=BsonDocument. . .
     // add new typestypeBObjectId=BsonObjectIdtypeBInt32=BsonInt32typeBInt64=BsonInt64. . .
     // add new conversion functions, possibly following the existing interfacedeffromObjectId(oid: BObjectId):ObjectId=.. .
     defmakeObjectId(oid: ObjectId):BObjectId=.. .
     defgetObjectId(value: BValue):BObjectId=.. .
     deffromInt32(n: BInt32):Int=.. .
    . . .
     deffromInt64(n: BInt64):Long=.. .
    . . .
    }// define a backend componenttraitBsonBackendComponentextendsBackendComponent {
     overridevalbackend=BsonBackend}// define a trait with custom serializerstraitBsonBackendSerializers {
     // it should depend on the concrete BsonBackendComponent, not on generic BackendComponentthis:BsonBackendComponentwithTypesComponent=>importbackend._// and here we can use all custom functions defined in the custom backendimplicitvalobjectIdReadWriter:ReadWriter[ObjectId] = 
     ReadWriter.writing(backend.makeObjectId).reading {
     casebv: BObjectId=> backend.fromObjectId(bv)
     }
     implicitvalintReadWriter:ReadWriter[Int] =ReadWriter.writing(backend.makeInt32).reading {
     casebv: BInt32=> backend.fromInt32(bv)
     }
    . . .
    }// finally, define the pickler trait by mixing it all togethertraitBsonPicklerextendsDefaultPicklerwithBsonBackendComponentwithBsonBackendSerializersobjectBsonPicklerextendsBsonPickler

    注意,定制特性中定义的picklers将具有 GREATER 优先级,而不是从 DefaultPickler 特性继承的picklers。 因这里,在上面的特性中定义的intReadWriter 将被用来代替 PrimitiveReadWritersComponent 中定义的intReader/intWriter 对,它由 DefaultPickler 继承。

    你可以在官方BSON后端实现中找到这种技术的一个例子。

    创建自定义序列化程序

    picopickle在 TypesComponent 中定义 WriterReader 基本类型,这些类型称为序列化器。 它们负责将任意类型转换为它的后端表示形式,并分别返回。 构造自定义序列化程序最基本的方法是在和 Writer companion对象上使用 apply 方法,后者分别采用 PartialFunction[backend.BValue, T]T => backend.BValue,而非。

    在前面描述的序列化序列化对象中,称为序列化对象对象称为序列化对象对象。 虽然相关,但它们是不同的。 序列化程序对象是完全可选的,如果不需要则不必使用它们;另一方面,序列化程序是picopickle中的关键实体,不能与它们一起使用。

    任何 Writer,因为它接收总函数,应能够序列化它的相应类型的任何值。 然而,Reader 可能无法 MATCH 表示后端表示。 有关picopickle中错误处理的更多信息,请参见下面的。

    TypesComponent 还定义了一个名为 ReadWriter的组合序列化程序:

    typeReadWriter[T] =Reader[T] withWriter[T]

    它的辅助对象还提供了方便的工具来创建它的实例。 上面的示例可以用 ReadWriter 重写,如下所示:

    implicitvaldefinedByIntReadWriter:ReadWriter[DefinedByInt] =ReadWriter.reading {
     case backend.Extract.Number(x) =>DefinedByInt(x.intValue(), x.intValue().toString)
    }.writing {
     caseDefinedByInt(x, _) => backend.makeNumber(x)
    }

    如果你愿意,可以使用 switch reading/writing 分支顺序。

    提取器及后端转换

    Backend 特性提供了创建和重构后端表示对象的方法: 上面描述了 make*from*get* 方法。 不过,为了简化编写定制序列化程序,picopickle提供了一组工具来帮助你编写转换。 其中最基本的是提取器和后端转换 implicits。

    后端系统包含几个带有 unapply 方法,可以用于在 backend.BValue 中进行模式匹配,以便在你正匹配的特定版本中获得匹配( 如果确实是 backend.BObject,则获取低级别值)

    backend.makeObject(...) match {
     case backend.Extract.Object(m) =>// m is of type Map[String, backend.BValue]}

    所有主要后端表示变量都有提取器:

    • backend.Extract.Object
    • backend.Extract.Array
    • backend.Extract.String
    • backend.Extract.Number
    • backend.Extract.Boolean

    它们的unapply 实现只调用相应的get*from* 方法,如下所示:

    objectExtractors {
     objectString {
     defunapply(value: BValue):Option[String] = getString(value).map(fromString)
     }
    }

    可以使用后端上的make* 方法来完成相反的转换( 从原语到后端表示),但picopickle还提供了一组隐式修改器,它提供了 toBackend 方法。 这些decorator是在 backend.conversionImplicits 对象中定义的:

    importbackend.conversionImplicits._vals: backend.BString="hello world".toBackend// the 上面 is equivalent to this:vals: backend.BString= backend.makeString("hello world")

    这些隐式方法比 make* 函数更方便一些。

    转换器

    但是,低级别转换可能过于冗长,无法写入。 picopickle提供了定义如何将后端表示转换为所需 Scala 对象的声明性方法,反之亦然。 这是用转换器。

    转换器看起来很像 ReadWriter ;但是,它由两种类型和目标参数化:

    traitConverter[-T, +U] {
     deftoBackend(v: T): backend.BValuedefisDefinedAt(bv: backend.BValue):BooleandeffromBackend(bv: backend.BValue):U}

    转换器库定义几个隐式转换,允许任何转换器作为相应的ReaderWriter 或者 ReadWriter 使用:

    Converter[T, _] ->Writer[T]Converter[_, U] ->Reader[U]Converter[T, T] ->ReadWriter[T]

    使用和产生相同类型的转换器称为该类型的标识转换器。 当然,只有标识转换器可以用作 ReadWriter。 标识转换器具有方便的类型别名 Converter.Id[T]

    转换器库还定义了几个转换器,它允许组合到新的转换器,并提供基本的基本类型和数组。

    例如你可以手动为某些案例类定义转换:

    caseclassA(a: Boolean, b: Double)traitCustomSerializersextendsJsonPickler {
     importshapeless._importconverters._valaConverter:Converter.Id[A] = unlift(A.unapply) >>> obj {
     "a"-> bool ::"b"-> num.double ::HNil } >>>A.apply _
     valaReadWriter:ReadWriter[A] = aConverter // an implicit conversion is used here}

    这里的obj.apply 用于定义一个标识转换器 Boolean :: Double :: HNil>>> 操作"预置"和"附加"类 A的deconstructor和构造函数:

    A.unapply :A=>Option[(Boolean, Double)]
    unlift(A.unapply) :A=> (Boolean, Double)A.apply _ : (Boolean, Double) =>Aobj {
     "a"-> bool ::"b"-> num.double ::HNil} :Converter.Id[Boolean::Double::HNil]

    boolnum.double 分别是 BooleanDouble的标识转换器。

    >>> 操作使用一些不形状的魔法来转换上面的函数,这些函数使用和生成 HList的功能。 还有 >> 组合器,它不使用无形状和"prepends"和"附加"直接对应类型的函数:

    (A=>B) >>Converter[B, C] >> (C=>D) ->Converter[A, D]// compare:(A=> (T1, T2,.. ., Tn)) >>>Converter.Id[T1::T2::.. . ::Tn::HNil] >>> ((T1, T2,.. ., Tn) =>A) ->Converter.Id[A]

    请注意,这是非常安全的类型。 例如如果在 obj 中获得了顺序或者字段类型,那么它将不会编译。

    另外picopickle还为函数的andThen 提供了一个方便的隐式别名,也称为 >>>> 上的一起使用,可以轻松编写转换链。 例如,假设你有一个可以表示为字节 array的对象。 然后要将这里字节 array 序列化为Base64编码的字符串。 可以按如下方式编写:

    importjava.util.Base64importjava.nio.charset.StandardCharsetscaseclassData(s: String)objectData {
     defasBytes(d: Data) = d.s.getBytes(StandardCharsets.UTF_8)
     deffromBytes(b: Array[Byte]) =Data(newString(b, StandardCharsets.UTF_8))
    }valdataReadWriter:ReadWriter[Data] =Data.asBytes _ >>Base64.getEncoder.encodeToString _ >> str >>Base64.getDecoder.decode _ >>Data.fromBytes _

    >> 链链的函数序列在两个方向上自然定义了转换次序。

    阵列也有类似的东西。 例如可以将事例类序列化为字段的array:

    valaReadWriter:ReadWriter[A] = unlift(A.unapply) >>> arr(bool :: num.double ::HNil) >>>A.apply _

    自然,有针对同构数组和对象的转换器- 它们允许映射到 Scala 集合:

    valintListConv:Converter.Id[List[Int]] = arr.as[List].of(num.int)valvecTreeMapConv:Converter.Id[TreeMap[String, Vector[Double]]] = obj.as[TreeMap].to(arr.as[Vector].of(num.double))

    还有一个转换器,如果有相应的隐式实例可用,则委托给 ReaderWriter:

    valoptionStringConv:Converter.Id[Option[String]] = value[Option[String]]

    你可以在它的Scaladoc部分( 待办事项) 中找到更多的转换器。

    支持的类型

    默认情况下,picopickle为各种类型的序列化程序提供了很大的序列化程序,以便在序列化形式中尽可以能。 然后将这些序列化程序混合到单个pickler中。

    序列化程序在以下几个特性中定义:

    io.github.netvl.picopickle.{CollectionReaders, CollectionWriters, CollectionReaderWritersComponent}
    io.github.netvl.picopickle.{ShapelessReaders, ShapelessWriters, ShapelessReaderWritersComponent}
    io.github.netvl.picopickle.{PrimitiveReaders, PrimitiveWriters, PrimitiveReaderWritersComponent}
    io.github.netvl.picopickle.{TupleReaders, TupleWriters, TupleReaderWritersComponent}//generated automatically

    每个序列化程序都是可以重载的def 或者 val,因此你可以通过使用你自己的插件重写相应的隐式定义。

    下面的示例使用 JsonPickler,因此隐式地假定类似

    importio.github.netvl.picopickle.backends.jawn.JsonPickler._

    出现在代码中。

    基本类型和基本类型

    picopickle原生支持所有基元类型和基本类型的序列化:

    writeString(1:Int) shouldEqual "1"writeString(2L:Long) shouldEqual "2"writeString(12.2:Double) shouldEqual "3"writeString('a') shouldEqual ""a""writeString("hello") shouldEqual ""hello""writeString(true) shouldEqual "true"writeString(false) shouldEqual "false"writeString(null) shouldEqual "null"writeString('symbol) shouldEqual ""symbol""

    默认情况下,字符序列化为字符串,但是,集合后端重定义了这里行为。

    只要有类型参数的序列化程序,picopickle也可以序列化 Option[T]Either[L, R]:

    writeString(Some(1)) shouldEqual "[1]"writeString(None) shouldEqual "1"writeString(Left("hello")) shouldEqual """[0,"hello"]"""writeString(Right('goodbye)) shouldEqual """[1,"goodbye"]"""

    可选值在它们是案例类定义的一部分时也特别处理;参阅下面的详细说明。

    请注意,Either[L, R] 序列化格式不是最终的,可以在将来的版本中更改。

    数字和精度

    大多数JSON库表示数字为 64位 浮点数,换句话说,Double,但某些数值不适合 Double,并且出现舍入现象:

    80000000000000000.0 shouldEqual 80000000000000008.0// does not throw

    为了将数字精确地表示为可能的picopickle,默认情况下序列化所有无法表示为字符串的Long,而不是字符串:

    writeString(80000000000000000L) shouldEqual ""80000000000000000""writeString(Double.PositiveInfinity) shouldEqual "Infinity"

    在添加 BigInt/BigDecimal 处理程序时,可能会使用相同的机制。

    然而,在某些后端,这种行为可以被覆盖,如在官方BSON后端中所做的那样。

    元组

    元组被序列化为数组:

    writeString((1, true, "a")) shouldEqual "[1,true,"a"]"

    唯一的例外是零项的元组,通常称为 Unit。 它被序列化为一个空对象:

    writeString(()) shouldEqual "{}"

    当然,元组的所有元素也必须是可以序列化的。

    元组序列化实例作为构建过程的一部分生成,目前只支持长度最高和包括 22的元组。

    集合

    支持大多数 Scala 集合库类,包括 Iterable 下的所有抽象类以及数组:

    writeString(Iterable(1, 2, 3, 4)) shouldEqual "[1,2,3,4]"writeString(Seq(1, 2, 3, 4)) shouldEqual "[1,2,3,4]"writeString(Set(1, 2, 3, 4)) shouldEqual "[1,2,3,4]"writeString(Map(1->2, 3->4)) shouldEqual "[[1,2],[3,4]]"writeString(1::2::3::Nil) shouldEqual "[1,2,3]"writeString(Vector(1, 2, 3)) shouldEqual "[1,2,3]"wrtieString(TreeMap(1->2, 3->4)) shouldEqual "[[1,2],[3,4]]"writeString(Array(1, 2, 3)) shouldEqual "[1,2,3]"

    可变集合也可以是 [de] 序列化的。

    映射像两个元素元组的iterables一样序列化,即为两个元素数组的数组。 但是,如果映射具有字符串键( 静态确定的),则将它的序列化为对象:

    writeString(Map("a"->1, "b"->2)) shouldEqual """{"a":1,"b":2}"""

    使用字符串键序列化映射的上述行为是默认行为,但可以扩展。 请参见下面。

    如果你使用的抽象集合类型如 SeqSet 或者 Map,picopickle将完美地工作。 但是,如果使用具体集合类型,则可能存在问题。 对于大多数主要的具体实现,picopickle有很多实例,但并非所有的实例都有。 如果你需要库中不存在的东西,可以自由地提交问题。

    使用非字符串键的映射序列化

    json通常不允许使用非字符串值作为对象键,picopickle通过它的BObject 表示来强制这个限制,它需要字符串键。 但是,这有时会过于限制,尤它的是在 Scala 类型的语言中,并且由于这种常见模式。

    例如,对于 Scala 项目来说,对于不同类型的标识符来说,有一个新的或者多个 String 是不常见的:

    caseclassPostId(id: String)caseclassUserId(id: String)

    或者,也可以有这样的简单值类,它不包装 String,但是可以很容易地转换为字符串:

    caseclassEntityPath(elems: Vector[String]) {
     overridedeftoString= elems.mkString("/")
    }objectEntityPath {
     deffromString(s: String) =EntityPath(s.split("/").toVector)
    }

    有时希望将这些类作为映射中的键:

    typeUserMap=Map[UserId, User]typeEntityLocations=Map[EntityPath, Entity]

    一个会自然地希望这些映射具有基于对象的表示( 而不是数组的array ),因为密钥很容易转换和从字符串。 但是,在picopickle中,只有 Map[String, T] 类型的映射可以直接序列化为对象。

    为了允许这种 Pattern,picopickle提供了一种定义映射键的定制转换器的方法。 使用类型为 T的映射序列化或者反序列化,并且作用域中存在类型为 ObjectKeyReader[T]/ObjectKeyWriter[T]/ObjectKeyReadWriter[T] 的实例时,它将被用于从 T ( 反之亦然) 获取一个用于对象键的String:

    implicitvaluserIdObjectKeyReadWriter=ObjectKeyReadWriter(_.id, UserId)// below a `_.toString` conversion is implicitly usedimplicitvalentityPathObjectKeyReadWriter=ObjectKeyReadWriter(EntityPath.fromString)
    write[UserMap](Map(UserId("u1") ->.. ., UserId("u2") ->.. .)) shouldEqual 
     Map("u1"->.. ., "u2"->.. .)
    write[EntityLocations](Map(EntityPath(Vector("a", "b")) ->.. ., EntityPath(Vector("a", "c")) ->.. .)) shouldEqual
     Map("a/b"->.. ., "a/c"->.. .)// reading works just as well

    但是,在大型基代码中,由大量不同的类共享一个pickler很容易在项目的它的他部分中中断序列化格式。 允许控制这里picopickle支持对未知键类型的自动映射序列化禁用自动映射功能。 然后需要定义这里特定类型的对象密钥序列化程序,或者显式允许将这里类型的映射作为 array 序列化。 你需要创建一个自定义pickler并将 MapPicklingDisabledByDefault 特性混合到其中:

    objectCustomPicklerextendsCollectionsPicklerwithMapPicklingDisabledByDefault// won't compile because there is no ObjectKeyWriter[Int] in scope and serialization of maps// with Int keys is not allowedwrite(Map(1->"a", 2->"b")) // ---objectCustomPicklerextendsCollectionsPicklerwithMapPicklingDisabledByDefault {
     implicitvalintObjectKeyReadWriter=ObjectKeyReadWriter(_.toInt)
    }// works because we have defined an object key serializer for Intwrite(Map(1->"a", 2->"b")) shouldEqual Map("1"->"a", "2"->"b")// ---objectCustomPicklerextendsCollectionsPicklerwithMapPicklingDisabledByDefault {
     implicitvalintObjectKeyAllowed= allowMapPicklingWithKeyOfType[Int]
    }// works because we explicitly allowed maps of type Map[Int, T] to be serialized as an array of arrayswrite(Map(1->"a", 2->"b")) shouldEqual Vector(Vector(1, "a"), Vector(1, "b"))

    注意,如果在上面的代码中允许 of,将对应类型的对象密钥序列化器强制使用 picopickle。 但是,在未来版本中,这将是固定的;第二,它仍然不允许使用一个数组作为数组,偶然地将序列序列化为数组数组,这看起来很可能是引入这样的中断更改的format。

    密封特性层次

    picopickle支持密封trait层次结构( 某事物) ( 即case类)的自动序列化,可能继承了密封特性。 换句话说,picopickle可以序列化代数数据类型。

    最简单的例子是独立案例对象和案例类:

    caseobjectAcaseclassB(x: Int, y: A)
    writeString(A) shouldEqual "{}"writeString(B(10, A)) shouldEqual """{"x":10,"y":{}}"""

    默认情况下,picopickle将事例类序列化为具有键作为字段名称的对象。 案例对象被序列化为空对象。

    Case类和对象可以具有密封的特性作为它的父对象:

    sealedtraitRootcaseobjectAextendsRootcaseclassB(x: Int, y: Boolean)caseclassC(name: String, y: Root) extendsRoot

    将序列化类型显式设置为 Root ( 或者传递 Root 类型的值,但不传递某些具体子类) 时,将序列化为具有 discriminator密钥的对象:

    writeString[Root](A) shouldEqual """{"$variant":"A"}"""writeString[Root](B(10, true)) shouldEqual """{"$variant":"B","x":10,"y":true}"""writeString[Root](C("me", A)) shouldEqual """{"$variant":"C","name":"me","y":{"$variant":"A"}}"""

    如果不明确请求 Root,类将被序列化,如它们不是某事物的一部分:

    writeString(B(10, true)) shouldEqual """{"x":10,"y":true}"""

    但是,这并不是问题,因为如果使用密封特性,通常有类型的变量,而不是它的子类型。

    密封特性层次结构是使用无形 LabelledGeneric 隐式实例和一些自定义 MACROS 处理字段重命名和默认值( 它们都不受shapeless的原生支持) 实现的。

    更改鉴别器密钥

    通过重写 discriminatorKey 定义的字段,可以自定义无形序列化器使用的鉴别器密钥。 io.github.netvl.picopickle.SealedTraitDiscriminator 特性( 它的默认值是 "$variant" ):

    objectCustomPicklerextendsJsonPickler {
     overridevaldiscriminatorKey="$type"}// STH is from the example 上面CustomPickler.writeString[Root](A) shouldEqual """{"$type":"A"}"""

    当然,如果你想要,可以将它提取成单独的特性并将它的混合到不同的picklers中。

    或者,从 0.2.0开始,你可以通过在密封特性上放置 @discriminator 注释来指定特定密封特性层次结构的标识符:

    importio.github.netvl.picopickle.discriminator@discriminator("status") sealedtraitRootcaseobjectStoppedextendsRootcaseclassRunning(name: String) extendsRootwriteString[Root](Stopped) shouldEqual """{status:"Stopped"}"""writeString[Root](Running("me")) shouldEqual """{status:"Running","name":"me"}"""

    如果存在 @discriminator 注释,则它的值将用作鉴别器键;否则,discriminatorKey pickler字段的默认值将使用。

    可选字段的序列化

    在:类的字段中,该字段的序列化方式不同于常规选项: if字段的值为 None,则对应的键将从序列化数据中消失,如果是 Some(x),则键将存在,并且其值将为 x,不带额外的array 层:

    caseclassA(name: String, x: Option[Long])
    writeString(A("absent")) shouldEqual """{"name":"absent"}"""writeString(A("present", Some(42L)) shouldEqual """{"name":"present","x":42}"""

    这种方法可以轻松地实现数据结构的演化- - 总是可以在更新之前添加一个 Option 字段和数据序列化,将一个 None 放入新字段。

    如果可选字段再次包含选项:

    caseclassA(x: Option[Option[Long]])

    然后,"外部"选项按上面的步骤序列化,"内部"选项序列化为空 array,就像在它的他上下文中序列化选项:

    writeString(A(None)) shouldEqual """{}"""writeString(A(Some(None))) shouldEqual """{"x":[]}"""writeString(A(Some(Some(10L)))) shouldEqual """{"x":[10]}"""

    重命名字段和密封特性变体

    picopickle还提供重命名字段和变量标签的能力。 这可以通过使用 @key 注释注释字段来完成:

    importio.github.netvl.picopickle.keysealedtraitRoot@key("0") caseobjectA@key("1") caseclassB(@key("a") x: Int, @key("b") y: Boolean)
    writeString[Root](A) shouldEqual """{"$variant":"0"}"""writeString[Root](B(10, false)) shouldEqual """{"$variant":"1","a":10,"b":false}"""

    键始终是字符串,尽管。

    case类字段的默认值

    picopickle还尊重在案例类中定义的默认值,从而简化了数据类的更改。 如果字段具有默认值并且序列化对象不包含相应的字段,则将使用默认值:

    caseclassA(n: Int=11)
    readString[A]("""{"n":22}""") shouldEqual A(22)
    readString[A]("""{}""") shouldEqual A()

    可以看到,这个机制自然地干扰了可选的字段处理。 picopickle按以下方式解决这里冲突: 如果对应键没有任何值,并且为该字段设置默认值,则优先于选项处理。 当有一个具有默认值除 None 之外的可选字段时,这会影响相当少的情况:

    caseclassA(n: Option[Int] =Some(10))
    readString[A]("{}") shouldEqual A(Some(10)) // not A(None)

    这在这种情况下通常是预期的。

    可变参数

    版本 0.2.0 picopickle支持读取和写入带有变量参数的事例类。 传递给此类类的所有参数将序列化为 array:

    caseclassA(x: Int*)
    writeString(A(1, 2, 3)) shouldEqual """{"x":[1,2,3]}"""

    当然,这个 array的所有元素都是用它们各自的序列化器序列化的。

    null

    就像众所周知的Null 值,通常会导致问题,在惯用 Scala 代码中不鼓励这样做。 有时,有时你需要与使用null的外部系统交互。 JSON也有空值。 因为picopickle支持空( 它甚至拥有 BNull 作为基本的后端类型之一),但是它还提供了控制如何处理null的方法。

    ReaderWriter 特性不包含任何特殊的逻辑来处理空值。 但是,通过它们的伴生对象创建的ReaderWriter 实例确实具有这样的逻辑: 它们将null处理委托给 NullHandlerComponent 提供的NullHandler 实例。 NullHandler 是以下结构的特征:

    traitNullHandler {
     defhandlesNull:BooleandeftoBackend[T](value: T, cont: T=> backend.BValue): backend.BValuedeffromBackend[T](value: backend.BValue, cont: backend.BValue=>T):T}

    也就是说,它是一种检查传递值的预处理器,[de] 可以特别序列化它们或者禁止 [de] 序列化。

    默认情况下,picopickle在(。DefaultPickler 包含 DefaultNullHandlerComponent ) 中允许空值。 也就是说,如果序列化空,则用 backend.BNull 无条件表示它,backend.BNull 将反序列化为。

    还有另一个 NullHandlerComponent 实现,即 ProhibitiveNullHandlerComponent,它禁止序列化 null,如果在 Scala 对象或者后端对象中遇到空值,则抛出异常。 如果不需要与使用空值的某些外部系统兼容,那么可以对所需的pickler进行扩展,重写默认的null处理程序:

    traitMyJsonPicklerextendsJsonPicklerwithProhibitiveNullHandlerComponent

    只要使用 Reader/Writer 对象或者转换器创建自定义序列化程序,空处理行为将对pickler处理的所有类型一致。

    精确编号序列化

    某些后端不允许精确地序列化某些数字。 例如大多数JSON实现都表示所有带有 64位 浮点数的数字,换句话说,Double。 例如 Scala Long 不能用 Double 来精确地表示。 对于大整数和小数,这是更为。

    picopickle后端提供了使用这些方法尽可能精确地序列化任意数字的方法:

    defmakeNumberAccurately(n: Number):BValuedeffromNumberAccurately(value: BValue):Number

    你可以看到,这些方法接受并返回 BValue 而不是 BNumber。 后端实现可以利用这一点,并将长数字序列化为字符串或者以它的他格式保持精度。 内置的数字序列化程序默认情况下使用这些方法。

    如果picopickle可以在 Double 中精确地表示,那么它还提供了一个特殊特性 DoubleOrStringNumberRepr,它提供了存储数字的方法,如果它能。 这种特性在编写基于json的后端时非常有用。

    值类

    使用 picopickle,你可以选择序列化值类( 例如。 将 AnyVal 类直接作为值进行扩展的值,绕过对象的常用贴图表示形式。 要启用这里行为,请使用 ValueClassReaderWritersComponent 扩展你的pickler:

    traitMyJsonPicklerextendsJsonPicklerwithValueClassReaderWritersComponentimportMyJsonPickler._classA(valx:Int) extendsAnyValwriteString(A(10)) shouldEqual "10"// not"""{"x":10}"""

    正式后端

    集合

    picopickle有几个"官方"后端。 其中一个由 picopickle-core 库提供,允许序列化成为集合的树。 这里后端可以立即使用,仅具有 core 依赖项:

    libraryDependencies +="io.github.netvl.picopickle"%%"picopickle-core"%"0.3.2"

    在这个后端,下面的AST映射包含:

    BValue -> Any
    BObject -> Map[String, Any]
    BArray -> Vector[Any]
    BString -> String
    BNumber -> Number
    BBoolean -> Boolean
    BNull -> Null

    在后端,后端表示与目标媒体一致,因此除了基本 read/write 之外不需要转换方法。

    这个后端还调整默认的Char 序列化程序来编写和读取字符作为 Char的,而不是 String的( 这是默认行为)。

    注意,它的他所有( 甚至它的他集合) 仍然按常规序列化,例如,元组表示为矢量和映射:

    write((2:Int, "abcde":String)) ->Vector(2, "abcde")
    write(Map(1->2, 3->4)) ->Vector(Vector(1, 2), Vector(3, 4))

    集合pickler也不使用精确编号序列化,因为它的后端表示已经尽可能准确。

    JSON"

    另一个官方后端用于转换到 JSON。 JSON解析是用 jawn 库完成的;然而,JSON呈现是自定义的。 这里后端可以在 picopickle-backend-jawn 中使用:

    libraryDependencies +="io.github.netvl.picopickle"%%"picopickle-backend-jawn"%"0.3.2"

    这个后端的AST是在 io.github.netvl.picopickle.backends.jawn.JsonAst 由与JSON基本类型对应的几个基本类类组成。 未提供用于JSON操作的其他实用工具;如果需要,则应使用其他库。

    JSON后端另外提供了两种方法: readAst/writeAst 将 JSON AST从字符串转换为字符串,而 readString/writeString 则直接从字符串序列化和转换为字符串。 通常,最后一对方法是希望使用JSON序列化时所要使用的方法。

    不支持流序列化,因为后端( 并不是每个后端支持流,例如集合后端't的抽象性,因这里它需要完全不同的体系结构。

    Another AST库定义了另一个官方后端,用于转换到 BSON AST。

    libraryDependencies +="io.github.netvl.picopickle"%%"picopickle-backend-mongodb-bson"%"0.3.2"

    在这个后端,下面的AST映射包含:

    BValue -> BsonValue
    BObject -> BsonDocument
    BArray -> BsonArray
    BString -> BsonString
    BNumber -> BsonNumber
    BBoolean -> BsonBoolean
    BNull -> BsonNull

    BSON后端还定义了其他类型,如下所示:

    BObjectId -> BsonObjectId
    BInt32 -> BsonInt32
    BInt64 -> BsonInt64
    BDouble -> BsonDouble
    BDateTime -> BsonDateTime
    BBinary -> BsonBinary
    BSymbol -> BsonSymbol

    后端提供了从 Scala 核心类型转换为这些类型的其他功能:

    deffromBinary(bin: BBinary):Array[Byte]
     defmakeBinary(arr: Array[Byte]):BBinarydefgetBinary(value: BValue):Option[BBinary]
     deffromObjectId(oid: BObjectId):ObjectIddefmakeObjectId(oid: ObjectId):BObjectIddefgetObjectId(value: BValue):Option[BObjectId]
     deffromDateTime(dt: BDateTime):LongdefmakeDateTime(n: Long):BDateTimedefgetDateTime(value: BValue):Option[BDateTime]
     deffromSymbol(sym: BSymbol):SymboldefmakeSymbol(sym: Symbol):BSymboldefgetSymbol(value: BValue):Option[BSymbol]
     deffromInt32(n: BInt32):IntdefmakeInt32(n: Int):BInt32defgetInt32(value: BValue):Option[BsonInt32]
     deffromInt64(n: BInt64):LongdefmakeInt64(n: Long):BInt64defgetInt64(value: BValue):Option[BsonInt64]
     deffromDouble(n: BDouble):DoubledefmakeDouble(n: Double):BDoubledefgetDouble(value: BValue):Option[BsonDouble]

    backend.BsonExtract 对象中有相应的提取器,而后端转换implicits在 backend.bsonConversionImplicits 中定义:

    Reader {
     case backend.BsonExtract.ObjectId(oid) =>// oid: backend.BObjectId == org.bson.BsonObjectId }
     importbackend.bsonConversionImplicits._valbin: backend.BBinary=Array[Byte](1, 2, 3).toBackend

    后端重写数字读取器和编写器将 Scala 编号序列化为可能的最小类型,换句话说,ByteShortInt 序列化为 BInt32LongBInt64FloatDouble 序列化为 xsr。 你可以看到在这个后端中,不需要使用额外的度量来准确地序列化数字。

    这个后端还提供 Array[Byte]SymbolDateObjectId 类型的序列化程序,它们分别序列化为 BBinaryBSymbolBDateTimeBObjectId

    最后,这个后端为所有 BValue 子类型提供身份序列化程序,即将 BValue 序列化为 BValueBString 作为 BStringBInt64 作为 BInt64 等等。

    错误句柄

    序列化是简单且永不失败的,因为序列化通常是错误的,因为序列化的表示通常具有自由形式结构,而不是静态映射到它的表达。

    picopickle有一个特殊的异常类型,在反序列化错误时抛出。 这里异常在 ExceptionsComponent 中定义如下:

    caseclassReadException(message: String, data: backend.BValue, cause: Throwable=null)
     extendsBaseException(message, cause)
     objectReadException {
     defapply(reading: String, expected: String, got: backend.BValue):ReadException=ReadException(s"reading $reading, expected $expected, got $got", data = got)
     }

    当某些类型的反序列化尝试与所请求的类型不兼容时,异常将包含关于正在读取的内容。

    readString[Int]("""{"some":"thing"}""")
    io.github.netvl.picopickle.ExceptionsComponent$ReadException: reading number, expected number or string containing a number, got JsonObject(Map(some ->JsonString(thing)))

    你可以使用自己的反反模式来参与这个异常处理。 ReaderReadWriter 有一些创建反序列化的方法,允许你使用自定义消息进行错误:

    caseclassA(name: String)// Pre-defined message format, like 上面Reader.reading[A] {
     case backend.Extract.String(s) =>A(s)
    }.orThrowing(whenReading ="A", expected ="string")// Arbitrary custom messageReader.reading[A] {
     case backend.Extract.String(s) =>A(s)
    }.orThrowing(v => s"Got $v instead of string when reading A")// ReadWriters also can be customizedReadWriter.writing[A](_.name.toBackend)
    . reading { case backend.Extract.String(s) =>A(s) }
    . orThrowing(whenReading ="A", expected ="string")// Works in any orderReadWriter.reading[A] { case backend.Extract.String(s) =>A(s) }
    . orThrowing(whenReading ="A", expected ="string")
    . writing(_.name.toBackend)

    在上面构造的读取器中,当用于读取的部分函数未定义时,将引发错误。 也就是说,下面的读取器永远不会引发 ReadException:

    Reader.reading[A] {
     case value =>A(backend.fromString(value.asInstanceOf[BString]))
    }.orThrowing(whenReading ="A", expected ="string")

    如果没有提供字符串,它将抛出一个 ClassCastException

    如果仍需要对读取器使用捕获所有部分函数,你可以自己抛出 ReadException:

    Reader[A] {
     case value =>if (value.isInstanceOf[String]) A(backend.fromString(value.asInstanceOf[BString])
     elsethrowReadException(reading ="A", expected ="string", got = value)
    }

    虽然上面的例子是绝对虚构的,但它有合理的用例。

    其他后端实现可能继承 ExceptionsComponent.BaseException 来实现自定义错误。 例如在JSON后端中完成Jawn解析异常。

    最后,Pickler 特性提供 tryRead() 方法,返回 Try[T] 而不是由 read() 返回的T。 这里方法从不抛出任何异常,而是将它们作为 Try[T]Failure 变体返回。 序列化程序对象也有这样的方法,以及带有自定义序列化方法的官方后台,如jawn的tryReadString()

    限制

    由于picopickle依赖于正在序列化的类型的static 知识,所以不支持以任何形式序列化 Any。 但是,按照我所说的,它的设计不允许为 Any 编写一个使用反射的序列化程序。 然而,这甚至不是计划中的。

    它似乎试图序列化密封特性层次结构,它的中密封特性本身有一个类型参数使编译器死难。 然而,常规参数化的案例类。

    不支持带有循环循环的对象图,并且会导致堆栈溢出。 这通常不是问题,因为只有在至少有一部分可以变时才能构造这样的图( 比如 )。 idiomatic var 字段或者可变集合( 在惯用 Scala 代码中不鼓励)。

    由于 Scala 反射/宏工作的限制,如果这些类形成密封的特性层次,最好不要在同一位置定义序列化程序。 例如类似这样的内容将不起作用:

    objectSerializers {
     importSomePickler._sealedtraitRootcaseclassA(x: Int) extendsRootcaseobjectBextendsRootimplicitvalrootReadWriter=ReadWriter[Root]
    }

    因为这是不可能编译的,因为在这里,Root的封闭特征层次是不可能的,在这里,我们在这里。 如果要为类生成序列化程序,请在另一个对象中编写它们:

    objectClasses {
     sealedtraitRootcaseclassA(x: Int) extendsRootcaseobjectBextendsRoot}objectSerializers {
     importSomePicker._importClasses._implicitvalrootReadWriter=ReadWriter[Root]
    }

    计划

    • 考虑添加对更多类型的支持
    • 考虑添加更多转换器( 比如。 对于元组)
    • 为转换中的错误处理添加适当的支持
    • 添加更多测试
    • 添加更多文档

    变更日志

    0.3.2

    • 已经更新 Scala 到 2.12.3

    0.3.0

    • 已经更新 Scala 到 2.11.8
    • 添加了对序列化值类作为值的支持

    0.2.1

    • 更新形状为 2.3.0,macroparadise,jawn到 0.8.4,bson到 3.2.2,Scala 到 2.10.6
    • 切换到宏compat代替手工编写的宏 API 2.10和 2.11

    0.2.0

    • 更新到 2.2.3,jawn至 0.8.8,Scala 至 2.11.7
    • 固定支持 varargs ( 无形状更新的结果)
    • 改进的读接口( 添加 readOrElse 方法和更改现有代码以依赖于它)
    • 添加了适当的错误处理( #2 )
    • 新增了基于bson的backend ( #6 )
    • 为根据某事物而改变某事物的支持( #7 )

    0.1.3

    • 添加了序列化程序对象特性( #5 )
    • 添加了将任意类型序列化为映射键的支持,提供了转换器( #4 )

    0.1.2

    • 更新 Scala 2.10次版本( 4 -> 5 )

    0.1.1

    • 伙伴( #1 ) 中带有重载 apply 方法的类的固定处理

    0.1.0

    • 更多序列化程序实例
    • 为精确编号序列化添加了泛型处理
    • 添加的收藏后端
    • 支持递归类型
    • 添加的转换器
    • 针对自定义序列化程序的改进 API
    • 添加了重命名字段和密封特征变量的支持
    • 添加了对case类in默认值的支持
    • 为空值添加了适当的支持
    • 添加了测试生成器
    • 开始添加测试

    0.0.2

    • 为基元类型添加了更多实例
    • 改进的API

    0.0.1

    • 初始版本


    文章标签:BASE  ext  SHA  Extensible  Shape  序列化  Shapeless  

    Copyright © 2011 HelpLib All rights reserved.    知识分享协议 京ICP备05059198号-3  |  如果智培  |  酷兔英语