JSONITER-SCALA
Scala宏,用于编译时间生成安全和超快速的JSON编解码器。
JSONITER-SCALA的解析和序列化性能与:Borer,Circe,Circe与Jsoniter-Scala Booster,Jackson-Module-Scala,JSON4S-JACKSON,JSON4S-NATICE,JSON4S-NATICE,PLAY-JSON,PLAY-JSON,PLAY-JSON,JON-UPSCALA BOOSTER,SMITHYY4SING4SING4SYSY4SING4SYSY JOSY, Weepickle,Zio-Json,Zio-Schema-json库在以下环境中使用不同的JDK和GRAALVM版本:Intel®Core™Ultra 9 285K CPU @ 3.7GHz(Max 5.7GHz,200S BOOST),RAM 64GB DDR5-6400,UBUNTU 25.04(LINUX 6.14)(JUN-6.14)(linux 6.14),以及最新的21/21/21/21/21/21/21/21/21/21/及以来GRAALVM社区JDK 17/21/25-EA和GRAALVM JDK 17/21/25-EA*。
基准测试对浏览器的最新结果,将Jsoniter-Scala与:Circe,Circe与Jsoniter-Scala Booster,Play-Json,Play-Json,Jsoniter-Scala Booster,Smithy4S-JSON,Smithy4S-JSON,UPICKLE,UPICKLE,ZIO-JSON,ZIO-JSON和ZIO-SCHEMA-JSON和ZIO-SCHEMA-JSON汇编的2015年3月30日,应用于Intel®Core™Ultra 9 285K CPU @ 3.7GHz(最大5.7GHz,200s Boost),RAM 64GB DDR5-6400,在Windows 11 Pro(24H2)上。
内容
- 致谢
- 目标
- 功能和局限性
- 如何使用
- 已知问题
- 如何发展
致谢
该图书馆始于宏,该宏为Java Reader和Writer重新使用JSONITER(JSON-ITERATOR),但随后该图书馆演变为具有自己的解析和序列化的机制核心。
通过Scala Macros生成编解码器和主要细节的想法是从Kryo Macros(最初由Alexander Nemish开发的)借用的,并适应了JSON域的需求。
在Avystem Commons和Magnolia库中窥视了其他Scala宏特征。
java.time.*值是受DSL-JSON实现java.time.OffsetDateTime的启发的。
其他项目和博客文章有助于提供无与伦比的安全性和绩效特征来解析和序列化数字:
- Schubfach-将双打和浮动序列序列到文本表示的最有效,最简洁的方法
- 锈蚀 – 完全从文本表示形式中解析并翻倍的最有效方法
- Big -Math-使用
O(n^2)O(n^1.5)复杂性的BigInt和BigDecimal值的解析,而不是使用Java的实现,其中n是许多数字 - 詹姆斯·安哈尔特(James Anhalt
JVM平台的一堆SWAR技术技巧基于以下项目和博客文章:
- 鲍尔(Borer) – 用8字节单词快速解析JSON字符串
- SIMDJSON-通过8字节单词对数字的字符串快速检查
- FastDoubleParser-通过8字节单词的数字快速解析
- 约翰尼·李(Johnny Lee)的文章 – 快速串到几秒钟的转换
对所有贡献者的敬意:
目标
- 安全:通过失败快速的方法和清晰的报告,验证解析值,为次优的数据结构提供可配置的限制,安全默认值可用于DOS攻击,生成编解码器,生成在解析过程中创建固定类的实例以避免RCE攻击的实例
- Correctness : support the latest JSON format (RFC-8259), do not replace illegally encoded characters of string values by placeholder characters, parse numbers with limited binary representation doing half even rounding for too long JSON numbers, serialize floats and doubles to the shortest textual representation without loosing of precision
- 速度:直接从UTF-8字节到数据结构并返回JSON的解析和序列化,在不使用运行时反射或运行时代码生成的情况下疯狂地进行快速进行操作,中间ASTS,Hash Maps,但使用最少的分配和复制
-
生产力:使用一行宏来递归为复杂类型得出编解码器,以编译时间来最大程度地减少运行时问题的可能性,可选地打印生成的源作为编译器输出,以证明安全性和正确性,或者被重复使用,或者作为起点作为定制编码的实现,并立即将其编码为
nullscodecs,以立即进行编码。 - 人体工程学:具有预先配置的最安全和常见用法的默认用途,可以通过编译和运行时的配置实例轻松更改,结合编译时间注释和隐性,包含JSON的文本表示,提供漂亮的打印选项,在错误上提供六角形,以加快错误上下文的视图,以加快错误上下文的视野
功能和局限性
- json从
String,Array[Byte],java.nio.ByteBuffer,java.io.InputStream/java.io.FileInputStream中解析 - json序列化到
String,Array[Byte],java.nio.ByteBuffer,java.io.OutputStream/java.io.FileOutputStream - 通过指定位置和限制来支持从或写作到
Array[Byte]或java.nio.ByteBuffer的一部分 - 从
java.io.InputStream/java.io.FileInputStream中解析流json值和JSON阵列,而无需将所有输入和解析值保存在内存中 - 直接使用缓冲字节时,仅支持UTF-8编码,但是从/到
String序列化JSON并序列化JSON(虽然效率较低) - 用逃脱的字符解析JSON键和字符串值
- 可以生成编解码器,用于原始,盒装原始,枚举,弦,
String,BigInt,BigDecimal,bigdecimal,bigdecimal,Option,Either,java.util.UUID,java.time.*在这里列出 - 应该使用尚未在非第一个参数列表中定义默认值的主构造函数来定义类
- 非案例Scala类也支持,但是它们应该为主要构造函数的所有参数都有Getter登录器
- 作为地图键支持的类型是原始键,盒装原语,枚举,
String,BigInt,BigDecimal,java.util.UUID,java.time.* - 可以通过隐式
makeOrdering[K]实例来定制排序地图和集合的编解码器 - 核心模块支持读取和写入字节数组从/到base16和base64表示(RFC 4648)用于自定义编解码器
- 对于映射到字节数组,数字和
java.time.*类型 - 支持一阶和更高类型
- 以密封性状或Scala类作为基本类型和非抽象的Scala类或对象为叶类的2种ADT表示的支持:1st表示使用歧视器字段,并使用字符串类型的值,第二个使用字符串的字符串为对象和包装器JSON对象的字符串值,用于case Class Object for Case Class Object
- 映射到映射到地图的JSON值和JSON对象键的隐式分解值编解码器,允许注入您的自定义编解码器,以添加其他类型的支持或更改JSON中已支持类的JSON中的表示形式
- 支持上述所有类型的类型别名
- 生成的编解码器仅支持类实例的无环图
- 实例字段的顺序在生成的编解码器的序列化过程中保留
- 如果在类实例中检测到重复的键,则引发解析例外(除外)
- 通过投掷
NullPointerException错误禁止序列化null值 - 仅适用于可选或收集类型的
null值解析(这意味着None值或空收集)以及定义非默认值的字段 - 构造函数中定义的默认值的字段是可选的,需要其他字段(无需特殊注释)
- 值等于默认值的值,或者是空的选项/集合/数组未序列化以提供稀疏的输出
- 任何直接使用的值或作为构造函数参数默认值的一部分的值,都应具有
equals方法的正确实现(它主要涉及非案例类或具有自定义编解码器的其他类型) - 可以将字段注释为瞬态,也可以在构造函数中定义,以避免解析和序列化
- 可以在类的主要构造函数中序列化/解析字段名称
- 通过使用自定义编解码器可以读取和写入任何任意字节或原始值
- 解析异常总是报告
Array[Byte],java.nio.ByteBuffer,java.io.InputStream/java.io.FileInputStream的十六进制偏移,发生在其中发生,并受到内部Byte Bufferer Bufferer Interner byte Bufferer的误差部分影响 - 可通过字段注释能力配置的能力从/到字符串值读取数字字段
- 密钥和价值编解码器都专门使用原始图,而无需拳击/拆箱
- 从
java.io.InputStream/java.io.FileInputStream或序列化java.io.OutputStream/java.io.FileOuputStream时,无需额外缓冲 - 仅将黑匣子宏仅用于编解码器生成可确保您的类型永远不会更改
- 订购生成的代码以保留校验和最大化远程构建工具的命中率
- 能够使用编解码器范围的
CodecMakerConfig.PrintCodec类型打印编解码器生成的代码 - 不包括Scala的
scala-library(所有平台)和scala-java-time(替换JDKSjava.time._类型scala.js和Scala本地),对运行时的额外库无依赖关系 - 在Scala.js和Scala本机平台上,如果您需要对
UTC以外的时区的支持 - 编解码器和运行时配置实现
java.io.Serializable可在分布式计算中更容易使用 - 支持对另一个软件包的阴影支持特定发布版本
- 补丁版本是向后和向前兼容的,次要版本是向后兼容的
- 与CIRCE集成,以更快地解析/序列化和解码/编码到/从CIRCE AST
- 为不同的Scala版本发布:2.12、2.13和3.3
- JVM的Java 11+版本的支持
- 支持GRAALVM对本地图像的汇编
- 对所有受支持的Scala版本的Scala.js 1.0+的支持
- 支持Scala Native 0.5+的所有受支持的Scala版本
- 抑制对Scala 2.12和2.13发出的所有编解码器的所有Wartremover警告
可以在编译时设置可配置的选项:
- 能够从/到字符串值读取/写数字
- 能够读/写地图作为JSON数组
- 跳过意外领域或投掷解析例外
- 跳过具有空收集值的字段的序列化可以关闭以迫使它们的序列化
- 跳过具有空的可选值的字段的序列化可以关闭以迫使它们的序列化
- 跳过序列化的序列化值与主构造函数中定义的默认值匹配的字段可以关闭该值的序列化
- 在JSON输入中需要收集字段的能力
- 能够在JSON输入中需要具有默认值的字段
- 能够使用编译时间注释覆盖ADT和字段类的名称
- 从类及其字段的名称映射函数到JSON键,或将Java枚举值的名称映射到JSON字符串和背部的函数,包括在生成编解码器中所有字段的实施蛇case,kebab-case,camelcase,camelcase或pascalcase name的预定义函数
- ADT的鉴别器字段的可选名称
- 用于区分ADT类的歧视器字段值的映射函数
- 解析
BigDecimal值时,能够设置精度,比例限制和最大数字数量 - 解析
BigInt值时,可以设置最大数字数量的能力 - 解析位集时设置最大允许值
- 解析或地图时设置插入数量的限制
- 可以关闭递归数据结构的汇编错误
- 当歧视器不是第一个字段时,投掷运行时错误可以关闭
- 能够从/到ID编号解析/序列化Scala枚举的能力
- 能够得出可以区分
null字段值的编解码器,而丢失字段则是Some(None),而NoneOption[Option[_]] - 能够打开ADT中Scala对象的类似CIRCE的编码
- 能够禁用生成用于解码或编码的实施
- 需要定义默认值的字段
- 不需要检查字段重复时,能够生成较小,更高效的编解码器
- 能够嵌入非价值类的能力,而非价值类只有一个参数
在运行时更改解析和序列化的选项列表:
- 具有逃逸的Unicode字符的字符串序列化为ASCII兼容
- 产出及其步骤的缩进
- 默认情况下,投掷无堆栈的解析例外,以大大降低对性能的影响,而堆栈痕迹可以开发进行调试
- 关闭受内部字节缓冲区误差部分影响的十六进制倾倒,以降低对性能的影响
- 可以根据更大或较小的16字节线调整十六进制的尺寸
- 从
java.io.InputStream或java.nio.DirectByteBuffer解析时,内部输入缓冲区的最大大小 - 从
java.io.InputStream或java.nio.DirectByteBuffer解析时,内部输入缓冲区的首选大小 - 序列化至
java.io.OutputStream或java.nio.DirectByteBuffer时,内部输出缓冲区的首选尺寸 - 解析字符串值时炭缓冲区的最大大小
- 解析字符串值时首选的炭缓冲区大小
v2.13.5.2版本是支持JDK 8+和本机映像汇编的最后一个版本,该版本具有较早版本的GRAALVM。
v2.13.3.2版本是支持Scala 2.11的最后一个版本。
v2.30.2版本是支持Scala本机0.4+的最后一个版本。
有关即将到来的功能和修复,请参阅提交和问题页面。
如何使用
假设您有以下数据结构:
case class Device ( id : Int , model : String ) case class User ( name : String , devices : Seq [ Device ])
将核心库带有“编译”范围和带有“编译内部”或“提供”范围的宏库中的SBT依赖列表:
libraryDependencies ++= Seq ( // Use the %%% operator instead of %% for Scala.js and Scala Native \" com.github.plokhotnyuk.jsoniter-scala \" %% \" jsoniter-scala-core \" % \" 2.37.0 \" , // Use the \"provided\" scope instead when the \"compile-internal\" scope is not supported \" com.github.plokhotnyuk.jsoniter-scala \" %% \" jsoniter-scala-macros \" % \" 2.37.0 \" % \" compile-internal \" )
在Scala Cli脚本的开头,使用“ DEP”范围作为核心库或compileonly.dep。
//> using dep \" com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.37.0 \" //> using compileOnly . dep \" com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros::2.37.0 \"
为需要解析或序列化的顶级类型提供编解码器:
import com . github . plokhotnyuk . jsoniter_scala . macros . _ import com . github . plokhotnyuk . jsoniter_scala . core . _ given userCodec : JsonValueCodec [ User ] = JsonCodecMaker .make
就是这样!您已经生成了com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec的实例,用于整个嵌套数据结构。如果您不打算隔离/将它们从/到JSON序列化(不是User的一部分),则无需为Device等内嵌套类(例如设备)提供中间编解码器,并且使用默认的或相同的派生配置对其编解码器。
现在将其用于从/到String的解析和序列化:
val user = readFromString[ User ]( \"\"\" {\"name\":\"John\",\"devices\":[{\"id\":1,\"model\":\"HTC One X\"}]} \"\"\" ) val json = writeToString( User ( \" John \" , Seq ( Device ( 2 , \" iPhone X \" ))))
当您的输入来自网络或磁盘时,更有效的方法是解析和序列化:
- 使用
readFromArray/writeToArray字节阵列 - 使用
readFromSubArray/writeToSubArray的字节子阵列 -
java.nio.ByteBuffer实例使用readFromByteBuffer/writeToByteBuffer -
java.io.InputStream/java.io.OutputStream实例使用readFromStream/writeToStream
此外,从字节解析将检查UTF-8编码并在畸形字节时丢弃错误。
要打印编解码器生成的代码,在make电话之前,将以下行添加到编解码器推导的范围中。
given CodecMakerConfig . PrintCodec with {}
有关JSONITER-SCALA的更多用例,请查看测试:
- jsoncodecmakerspec
- packagespec
- JSONREDERSPEC
- JSONWRITERSPEC
所有Scala 3仅通过此目录中的规格测试。
NOTE: Until official docs will be published, please, use all these tests as tutorials and how-tos to help in your
journey to become happy users. Also, they are recommended to skim through for checking of your expectation before
selection of this library among others.
您可以使用以下在线服务从JSON示例中生成数据结构的初始版本:
- JSON2CASECLASS
- JSON至scala-Case级
- JSON2Classes
- QuickType
另外,如果您拥有JSON模式,则以下在线服务可以为您生成相应的数据结构:
- JSON-SCHEMA-CASE级
- QuickType
以下库可以为您的现有数据结构生成JSON模式:
- Scala-jsonschema
与不同的Web框架和HTTP服务器集成的样本:
- akka-http
- 火焰
- 巨人
- http4s
- Pekko-HTTP
- 玩(与Netty Native Transport一起玩)
- 你
- Zio-HTTP
JSONITER-SCALA在OSS库中的使用:
- akka-http-json-将Scala中一些最好的JSON LIB与Akka HTTP整合在一起
- Bootzooka-一个项目,可以快速开始开发基于Scala的微服务或Web应用程序,而无需写登录,用户注册等。
- Caliban-纯粹的功能库,用于构建GraphQl服务器和Scala中的客户端
- Dijon-使用安全有效的AST表示支持对无模式的JSON的支持
- Geo -Scala- Geojson的核心AST和实用程序(RFC 7946)等等
- 铁 – Scala 3中用于精制类型的轻巧库
- JSONITER-SCALA-CIRCE- CIRCE助推器,用于更快的解析/序列化到/形式circe ast ast and dexoding/decoding/docoding
java.time._和BigInt类型 - kafka-serde-scala-隐式将Typeclass编码器转换为Kafka Serialializer,Deserializer,Serde
- logging4s- scala 3的结构性日志
- Neotype- Scala 3
- 宣誓 – 另一个scala -jwt库,其目的是增强用户体验
- PEKKO-HTTP-JSON-将Scala中一些最好的JSON LIB与Pekko HTTP集成
- play-json-jsoniter-提供了将
play.api.libs.json.JsValue实例转换为字节数组(或字节缓冲区或输出流)的最快方法 - Scalatest -JSON-具有适当平等和描述性错误消息的Scalatest匹配器
- Smithy4S -JSON- SCALA的史密斯工具协议
- STTP-您一直想要的Scala HTTP客户端!
- STTP -OAUTH2 -OAUTH2客户库在Scala中使用STTP实现
- Tapir-键入API说明
另外,对于其他OSS项目中的Dependents
对于所有依赖项目,建议使用SBT-Updates插件或Scala管家服务以跟上最新版本的使用。
已知问题
- 解析过程中JSON表示的长度没有验证。
如果您的系统可以接受过长的不信任输入,请在使用readFromStream或其他read...调用之前检查输入长度。
另外,如果您的输入是一个值或白空间单独的值,则考虑通过scanJsonArrayFromInputStream或scanJsonValuesFromInputStream而不是readFromStream对其进行解析。
-
makeMacro的配置参数以编译时间评估。它不需要对使用宏调用结果的其他代码的依赖,否则将报告以下汇编错误:
[error] Cannot evaluate a parameter of the \'make\' macro call for type \'full.name.of.YourType\'. It should not depend on
code from the same compilation module where the \'make\' macro is called. Use a separated submodule of the project
to compile all such dependencies before their usage for generation of codecs.
有时,Scala 2编译器可能无法编译make Macro调用,并带有相同的错误消息,用于配置,该配置尚未明确依赖于其他代码。在这些情况下
- 使用
make或make...无参数的宏通话 - 像在此pr中一样,将
make宏调用隔离在分离的对象中 - 将jsoniter-scala导入到本地,例如这里和这里
- 使用
sbt clean compile stage或sbt clean test stage,而不仅仅是sbt clean stage, - 如果在Intellij Idea中使用Mill的本机BSP支持,请使用
mill clean - 确保只有一个版本的Jsoniter-Scala在classPath上!具有不同的版本(例如,通过依赖关系)会触发此错误。使用
dependencyTree/dependencyGraph或类似的机制来查看是否是这种情况。如果发生这种情况,请使用DuberRules摆脱错误的版本。
- 如果在ADT定义的编译或其派生的编解码器中,可能会发生意外的编译器错误,如果它们嵌套在此处的某些类或功能中。
两种情况下的解决方法都是相同的:不要将ADT定义封闭到外部类,性状或函数中,而是使用外对象(而不是类)。
- Scala 3中的
make电话的编译时间配置对名称映射的可能表达式有限。
请使用单位测试中的CodecMakerConfig使用示例。
- 当重复使用
JsonReader或JsonWriter的同一实例时,嵌套解析或序列化例程可能发生意外解析或序列化错误:
scanJsonValuesFromStream[ String ](in) { s => readFromString[ String ](s) }
除了最嵌套的呼叫以外,解决方法正在使用重新入学解析或序列化例程。这将在每个重新输入呼叫上创建JsonReader或JsonWriter的新实例:
scanJsonValuesFromStreamReentrant[ String ](in) { s => readFromString[ String ](s) }
- scala.js不支持从Java源编译的Java枚举,因此将失败与以下错误链接到以下错误:
[error] Referring to non-existent class com.github.plokhotnyuk.jsoniter_scala.macros.Level
[error] called from private com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMakerSpec.$anonfun$new$24()void
[error] called from private com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMakerSpec.$anonfun$new$1()void
[error] called from constructor com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMakerSpec.<init>()void
[error] called from static constructor com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMakerSpec.<clinit>()void
[error] called from core module analyzer
Scala 2的解决方法是将JVM和其他平台的源拆分源,并为Scala.js和Scala Native使用Java Enum Myulation。
JVM的代码:
public enum Level { HIGH , LOW ; }
Scala.js和Scala本地的代码:
object Level { val HIGH : Level = new Level ( \" HIGH \" , 0 ) val LOW : Level = new Level ( \" LOW \" , 1 ) val values : Array [ Level ] = Array ( HIGH , LOW ) def valueOf ( name : String ) : Level = if ( HIGH .name() == name) HIGH else if ( LOW .name() == name) LOW else throw new IllegalArgumentException ( s \" Unrecognized Level name: $name \" ) } final class Level private ( name : String , ordinal : Int ) extends Enum [ Level ](name, ordinal)
对于Scala 3,对于所有平台,解决方法都相同:
enum Level extends Enum [ Level ] { case HIGH case LOW }
- Scala 3编译器无法为具有具体类型参数的通用类型提供匿名编解码器:
case class DeResult [ T ]( isSucceed : Boolean , data : T , message : String ) case class RootPathFiles ( files : List [ String ]) given JsonValueCodec [ DeResult [ Option [ String ]]] = JsonCodecMaker .make given JsonValueCodec [ DeResult [ RootPathFiles ]] = JsonCodecMaker .make
当前3.2.x版本的Scalac版本失败,重复定义错误如下:
[error] 19 | given JsonValueCodec[DeResult[RootPathFiles]] = JsonCodecMaker.make
[error] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error] |given_JsonValueCodec_DeResult is already defined as given instance given_JsonValueCodec_DeResult
解决方法正在使用编解码器的命名实例:
given codecOfDeResult1 : JsonValueCodec [ DeResult [ Option [ String ]]] = JsonCodecMaker .make given codecOfDeResult2 : JsonValueCodec [ DeResult [ RootPathFiles ]] = JsonCodecMaker .make
或私人类型的别名,具有某些特征given定义给定定义:
trait DeResultCodecs : private type DeResult1 = DeResult [ Option [ String ]] private type DeResult2 = DeResult [ RootPathFiles ] given JsonValueCodec [ DeResult1 ] = JsonCodecMaker .make given JsonValueCodec [ DeResult2 ] = JsonCodecMaker .make end DeResultCodecs object DeResultCodecs extends DeResultCodecs import DeResultCodecs . given
-
当前,
JsonCodecMaker.makeCALL无法为Scala 3不透明和工会类型提供编解码器。解决方法是针对JsonCodecMaker.make之前用implicit val定义的这些类型的自定义编解码器。呼叫,就像这里和这里一样。 -
如果ADT LEAF类/对象包含其简单名称中的点,则默认名称映射器将剥离名称到最后一个点字符。解决方法是在这里使用
@named注释:
sealed abstract class Version ( val value : String ) object Version { @ named( \" 8.10 \" ) case object `8.10` extends Version ( \" 8.10 \" ) @ named( \" 8.09 \" ) case object `8.09` extends Version ( \" 8.9 \" ) }
- 当将JSON字符串解析为数字或
java.time.*不支持ASCII字符编码的值。解决方法是使用自定义编解码器,将这些值解析为字符串,然后将它们转换为相应的类型,如以下方式:
implicit val customCodecOfOffsetDateTime : JsonValueCodec [ OffsetDateTime ] = new JsonValueCodec [ OffsetDateTime ] { private [ this ] val defaultCodec : JsonValueCodec [ OffsetDateTime ] = JsonCodecMaker .make[ OffsetDateTime ] private [ this ] val maxLen = 44 // should be enough for the longest offset date time value private [ this ] val pool = new ThreadLocal [ Array [ Byte ]] { override def initialValue () : Array [ Byte ] = new Array [ Byte ](maxLen + 2 ) } def nullValue : OffsetDateTime = null def decodeValue ( in : JsonReader , default : OffsetDateTime ) : OffsetDateTime = { val buf = pool.get val s = in.readString( null ) val len = s.length if (len <= maxLen && { buf( 0 ) = \'\"\' var bits, i = 0 while (i < len) { val ch = s.charAt(i) buf(i + 1 ) = ch.toByte bits |= ch i += 1 } buf(i + 1 ) = \'\"\' bits < 0x80 }) { try { return readFromSubArrayReentrant(buf, 0 , len + 2 , ReaderConfig )(defaultCodec) } catch { case NonFatal (_) => () } } in.decodeError( \" illegal offset date time \" ) } def encodeValue ( x : OffsetDateTime , out : JsonWriter ) : Unit = out.writeVal(x) }
- 请勿使用
implicit def和inline given方法来生成自定义代码。 Scala 3.5.0+显示了编译时间警告新的匿名类定义,每个内联站点将在某些inline given案例New anonymous class definition will be duplicated at each inline site,但是对于其他用例,编译器将默默生成重复的编解码器实例。为了减轻该编解码器生成方法的转换为def并明确推导自定义编解码器,如下所示:
object Tags { opaque type Tagged [ + V , + T ] = Any type @@ [ + V , + T ] = V & Tagged [ V , T ] def tag [ T ] : [ V ] => V => V @@ Tag = [ V ] => ( v : V ) => v } object Graph { import Tags .{ @@ , tag } def tagJsonValueCodec [ V , T ]( codec : JsonValueCodec [ V ]) : JsonValueCodec [ V @@ T ] = new JsonValueCodec [ V @@ T ] : // println(\"+1\") override def decodeValue ( in : JsonReader , default : V @@ T ) : V @@ T = tag[ T ](codec.decodeValue(in, default : V )) override def encodeValue ( x : V @@ T , out : JsonWriter ) : Unit = codec.encodeValue(x, out) override def nullValue : V @@ T = tag[ T ](codec.nullValue) trait NodeIdTag type NodeId = Int @@ NodeIdTag case class Node ( id : NodeId , name : String ) case class Edge ( node1 : NodeId , node2 : NodeId ) given JsonValueCodec [ Graph . NodeId ] = Graph .tagJsonValueCodec( JsonCodecMaker .make) given JsonValueCodec [ Graph . Node ] = JsonCodecMaker .make given JsonValueCodec [ Graph . Edge ] = JsonCodecMaker .make }
如何发展
随时在聊天,开放问题中提出问题,或通过创建拉动请求(对文档,代码和测试的改进得到高度赞赏)。
当前, gh-pages分支包含许多基准结果的历史记录数据,因此为避免使用10GB的10GB使用--single-branch分支选项仅获取源。
如果在叉子上开发,请确保下载git标签(SBT构建要求):
git remote add upstream git@github.com:plokhotnyuk/jsoniter-scala.git git fetch --tags upstream
构建scala.js和scala天然模块的先决条件是clang 18.x and node.js 16.x.以下命令顺序对我有用:
sudo apt install clang libstdc++-12-dev libgc-dev curl https://raw.githu*buser**content.com/creationix/nvm/master/install.sh | bash source ~ /.bashrc nvm install 16 node -v
获取可用依赖性更新的报告
sbt \" ;dependencyUpdates; reload plugins; dependencyUpdates; reload return \"
运行测试,检查覆盖范围和二进制兼容性
sbt -java-home /usr/lib/jvm/jdk-11 ++2.13.16 clean coverage jsoniter-scala-coreJVM/test jsoniter-scala-circeJVM/test jsoniter-scala-macrosJVM/test jsoniter-scala-benchmarkJVM/test coverageReport sbt -java-home /usr/lib/jvm/jdk-11 clean +test +mimaReportBinaryIssues
当心:Scala 2和Scala Scala 3的Scala Scala社区构建中包含JSONITER-SCALA。
运行JVM基准
在基准运行之前,请检查您的CPU是否在performance模式下工作(不是powersave One)。在Linux上使用以下命令以打印电流并设置performance模式:
cat /sys/devices/system/cpu/cpu * /cpufreq/scaling_governor for i in $( ls /sys/devices/system/cpu/cpu * /cpufreq/scaling_governor ) ; do echo performance | sudo tee $i ; done
然后使用以下方式查看您的CPU频率
cat /proc/cpuinfo | grep -i mhz
停止不需要的申请和服务。运行服务列表可以通过:
sudo service --status-all | grep \' \\[ + \\] \' sudo systemctl list-units --state running
然后清除缓存内存以提高系统性能。在不必重新启动系统的情况下清除Linux上清除缓存内存的一种方法:
sudo su free -m -h && sync && echo 3 > /proc/sys/vm/drop_caches && free -m -h
JMH工具的SBT插件用于基准测试,以查看其所有功能和选项,请检查SBT-JMH文档和JMH工具文档
了解如何在AlekseyShipilёV和Nitsan Wakart的博客中发布的JMH样品和JMH文章中编写基准测试。
可用选项的列表可以通过:
sbt jsoniter-scala-benchmarkJVM/clean \' jsoniter-scala-benchmarkJVM/Jmh/run -h \'
基准的结果可以以不同的格式存储: *.csv, *.json等。所有支持的格式都可以列出:
sbt jsoniter-scala-benchmarkJVM/clean \' jsoniter-scala-benchmarkJVM/Jmh/run -lrf \'
JMH允许使用不同的参考器运行基准测试,可以获取支持使用的列表(可能需要输入用户密码):
sbt jsoniter-scala-benchmarkJVM/clean \' jsoniter-scala-benchmarkJVM/Jmh/run -lprof \'
可以通过以下命令打印profiler选项的帮助( <profiler_name>应从上面的命令中替换为受支持的profiler的名称):
sbt jsoniter-scala-benchmarkJVM/clean \' jsoniter-scala-benchmarkJVM/Jmh/run -prof <profiler_name>:help \'
对于参数基准,可以通过-p选项设置参数的常数值:
sbt jsoniter-scala-benchmarkJVM/clean \' jsoniter-scala-benchmarkJVM/Jmh/run -p size=1,10,100,1000 ArrayOf.* \'
要使用以下命令来查看生成编解码器的分配率通过GC Profiler运行基准:
sbt jsoniter-scala-benchmarkJVM/clean \' jsoniter-scala-benchmarkJVM/Jmh/run -prof gc .*Reading.* \'
<p dir=\"aut
