jsoniter-scala
repository·master·Indexed 21 days ago
https://github.com/plokhotnyuk/jsoniter-scalaA Scala library providing ultra-fast, compile-time generated JSON codecs. It prioritizes safety and performance by avoiding runtime reflection, intermediate ASTs, and hash maps, focusing on direct processing between UTF-8 bytes and Scala data structures. Supports a wide range of types including primitives, java.time, collections, and case classes, with compatibility for Uber JARs, GraalVM Native Image, Scala JS, and Scala Native.
What's inside jsoniter-scala
- jsoniter-scala is a Scala library that uses macros to perform compile-time generation of safe and ultra-fast JSON codecs. It is designed to avoid runtime reflection, runtime code generation, intermediate ASTs, and hash maps, focusing instead on direct parsing and serialization between UTF-8 bytes and Scala data structures with minimal allocations.
Supported Scala types for codec generation
masterThe library can automatically generate codecs for the following types:
- Primitives: Primitives and boxed primitives.
- Standard Types:
String,BigInt,BigDecimal,java.util.UUID. - Time:
java.time.*(supports ISO-8601 representation). - Containers: Scala collections, arrays (including Scala 3 immutable arrays),
Option,Either, and tuples. - Advanced Types: Enums, module classes, literal types, value classes, and first-order/higher-kind types.
- Complex Structures: Case classes (where fields are any of the supported types), non-case classes (provided they have getter accessors for primary constructor arguments), and type aliases.
Requirements for defining classes for codec generation
masterTo ensure successful codec generation, follow these rules when defining your data classes:
- Primary Constructor: Classes should be defined with a primary constructor.
- Default Values: Avoid defining default values in non-first parameter lists of the primary constructor.
- Non-case Classes: If using non-case Scala classes, ensure they have getter accessors for all arguments in the primary constructor.
- Acyclic Graphs: Only acyclic graphs of class instances are supported. Recursive data structures will cause a compilation error unless specifically configured otherwise.
- Null Handling: Serialization of
nullvalues is prohibited and will throw aNullPointerException. Parsingnullis only allowed forOptiontypes, collection types, or fields with defined non-null default values.
Core goals of jsoniter-scala
masterThe library is built around five primary pillars:
- Safety: Fail-fast validation with clear reporting, configurable limits to prevent DoS attacks, and fixed-set class instantiation to prevent RCE attacks.
- Correctness: Full RFC-8259 support, precise number parsing (half-even rounding for long numbers), and shortest-textual-representation serialization for floats/doubles without precision loss.
- Speed: Direct UTF-8 byte processing without reflection or intermediate ASTs.
- Productivity: One-line macro derivation for complex types at compile-time, with the option to inspect generated source code.
- Ergonomics: Safe defaults, compile-time annotations/implicits, pretty-printing support, and hex dumps in error messages for better debugging.
Supported data types and formats for JSON parsing and serialization
masterjsoniter-scala supports a wide range of input and output formats for high-performance JSON processing:
Parsing from:
StringArray[Byte]java.nio.ByteBufferjava.io.InputStream/java.io.FileInputStream(supports streaming without loading the entire input into memory)
Serialization to:
StringArray[Byte]java.nio.ByteBufferjava.io.OutputStream/java.io.FileOutputStream
Key technical details:
- Partial processing: You can parse from or write to specific parts of an
Array[Byte]orjava.nio.ByteBufferby specifying a position and limit. - Encoding: Direct byte buffer operations only support UTF-8. For other encodings, the library falls back to
Stringprocessing, which is less efficient. - Streaming: Parsing from
InputStreamallows for processing streaming JSON values and arrays without high memory overhead.
Run and profile JVM benchmarks
masterBenchmarks are powered by the JMH (Java Microbenchmark Harness) tool via the sbt-jmh plugin.
System Preparation
To ensure accurate results on Linux, set your CPU to
performancemode and clear the system cache:# Set CPU to performance mode for i in $(ls /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor); do echo performance | sudo tee $i; done # Clear cache memory sudo su free -m -h && sync && echo 3 > /proc/sys/vm/drop_caches && free -m -hCommon Benchmark Tasks
- List options:
sbt jsoniter-scala-benchmarkJVM/clean 'jsoniter-scala-benchmarkJVM/Jmh/run -h' - List output formats:
sbt jsoniter-scala-benchmarkJVM/clean 'jsoniter-scala-benchmarkJVM/Jmh/run -lrf' - List profilers:
sbt jsoniter-scala-benchmarkJVM/clean 'jsoniter-scala-benchmarkJVM/Jmh/run -lprof' - Run with parameters: Use
-pto set constant values (e.g.,-p size=1,10,100). - GC Profiling: To see throughput with allocation rates, use
-prof gc .*Reading.*. - Async Profiler: Requires extracting binaries to
/opt/async-profilerand settingkernel.perf_event_paranoid=1andkernel.kptr_restrict=0via sysctl.
# Example: Run benchmark with a specific profiler and parameters sbt jsoniter-scala-benchmarkJVM/clean 'jsoniter-scala-benchmarkJVM/Jmh/run -p size=1,10,100,1000 ArrayOf.*' # Example: Run with GC profiler to see allocation rates sbt jsoniter-scala-benchmarkJVM/clean 'jsoniter-scala-benchmarkJVM/Jmh/run -prof gc .*Reading.*'- List options:
Install jsoniter-scala via Scala CLI
masterFor Scala CLI scripts, use the
depscope for the core library and thecompileOnly.depscope for the macros library.//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.39.1" //> using compileOnly.dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros::2.39.1"Run tests, coverage, and binary compatibility checks
masterUse the following sbt commands to run the test suite, generate coverage reports, and check binary compatibility across different Scala/JDK versions.
For Scala 2.13.18 on JDK 17: Runs clean, coverage, and tests for core, circe, macros, and benchmark modules.
For Scala 2.11 on JDK 11: Runs clean, tests, and generates a MiMa (Migration Manager) report to check for binary compatibility issues.
# Run tests and coverage for Scala 2.13.18 sbt -java-home /usr/lib/jvm/jdk-17 ++2.13.18 clean coverage jsoniter-scala-coreJVM/test jsoniter-scala-circeJVM/test jsoniter-scala-macrosJVM/test jsoniter-scala-benchmarkJVM/test coverageReport # Check binary compatibility for Scala 2.11 sbt -java-home /usr/lib/jvm/jdk-11 clean +test +mimaReportBinaryIssuesBuild and measure Uber Jar startup time
masterTo build a standard Scala Uber Jar using
scala-cliand measure its startup performance on Linux, use the following steps. This example is tested with Oracle GraalVM 25-dev.- Install necessary Linux tools and adjust kernel permissions for performance monitoring.
- Package the script
example01.scinto a JAR. - Run the JAR using
javawithsun-misc-unsafe-memory-access=allowenabled, wrapped inperf statto measure execution metrics.
sudo apt install linux-tools-common linux-tools-generic sudo sysctl kernel.perf_event_paranoid=1 scala-cli --power package --assembly example01.sc --force -o example01.jar ls -l ./example01.jar perf stat -r 100 java --sun-misc-unsafe-memory-access=allow -jar ./example01.jar > /dev/nullPublish artifacts locally
masterUse these commands to publish SNAPSHOT versions to your local repositories for testing with other applications.
# Publish to local Ivy repository sbt clean +publishLocal # Publish to local Maven repository sbt clean +publishM2Set up a development environment for jsoniter-scala
masterTo develop on a fork, ensure you download the git tags required by the sbt build. For building Scala.js and Scala Native modules, you must have Clang 18.x and Node.js 16.x installed.
Follow these steps to set up the environment on Linux:
- Add the upstream remote and fetch tags.
- Install system dependencies (Clang, libstdc++, libgc).
- Install Node.js 16 using NVM.
# Setup upstream remote git remote add upstream git@github.com:plokhotnyuk/jsoniter-scala.git git fetch --tags upstream # Install prerequisites (Linux example) sudo apt install clang libstdc++-12-dev libgc-dev curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash source ~/.bashrc nvm install 16Run Scala.js benchmarks
masterTo build and run Scala.js benchmarks, use JDK 17+ to build the
jsoniter-scala-benchmarkJSmodule.- Build the optimized JS:
sbt -DassemblyJSBenchmarks -java-home /usr/lib/jvm/jdk-17 +jsoniter-scala-benchmarkJS/fullOptJS. - Open the generated HTML reports in your browser from the
jsoniter-scala-benchmark/jsdirectory. - To merge results from multiple browsers into a single JSON file, use
jq.
# Build Scala.js benchmarks sbt -DassemblyJSBenchmarks -java-home /usr/lib/jvm/jdk-17 +jsoniter-scala-benchmarkJS/fullOptJS # Merge results using jq jq -s '[.[][]]' firefox/*.json > firefox.json- Build the optimized JS: