jsoniter-scala

repository·master·Indexed 21 days ago

https://github.com/plokhotnyuk/jsoniter-scala

A 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.

Tokens
8K
Snippets
23
Records
38
Agent score
24%

What's inside jsoniter-scala

  1. Overview of jsoniter-scala

    master
    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.
  2. Supported Scala types for codec generation

    master

    The 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.
  3. Requirements for defining classes for codec generation

    master

    To 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 null values is prohibited and will throw a NullPointerException. Parsing null is only allowed for Option types, collection types, or fields with defined non-null default values.
  4. Core goals of jsoniter-scala

    master

    The library is built around five primary pillars:

    1. Safety: Fail-fast validation with clear reporting, configurable limits to prevent DoS attacks, and fixed-set class instantiation to prevent RCE attacks.
    2. 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.
    3. Speed: Direct UTF-8 byte processing without reflection or intermediate ASTs.
    4. Productivity: One-line macro derivation for complex types at compile-time, with the option to inspect generated source code.
    5. Ergonomics: Safe defaults, compile-time annotations/implicits, pretty-printing support, and hex dumps in error messages for better debugging.
  5. Supported data types and formats for JSON parsing and serialization

    master

    jsoniter-scala supports a wide range of input and output formats for high-performance JSON processing:

    Parsing from:

    • String
    • Array[Byte]
    • java.nio.ByteBuffer
    • java.io.InputStream / java.io.FileInputStream (supports streaming without loading the entire input into memory)

    Serialization to:

    • String
    • Array[Byte]
    • java.nio.ByteBuffer
    • java.io.OutputStream / java.io.FileOutputStream

    Key technical details:

    • Partial processing: You can parse from or write to specific parts of an Array[Byte] or java.nio.ByteBuffer by specifying a position and limit.
    • Encoding: Direct byte buffer operations only support UTF-8. For other encodings, the library falls back to String processing, which is less efficient.
    • Streaming: Parsing from InputStream allows for processing streaming JSON values and arrays without high memory overhead.
  6. Run and profile JVM benchmarks

    master

    Benchmarks 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 performance mode 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 -h

    Common 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 -p to 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-profiler and setting kernel.perf_event_paranoid=1 and kernel.kptr_restrict=0 via 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.*'
  7. Install jsoniter-scala via Scala CLI

    master

    For Scala CLI scripts, use the dep scope for the core library and the compileOnly.dep scope 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"
  8. Run tests, coverage, and binary compatibility checks

    master

    Use 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 +mimaReportBinaryIssues
  9. Build and measure Uber Jar startup time

    master

    To build a standard Scala Uber Jar using scala-cli and measure its startup performance on Linux, use the following steps. This example is tested with Oracle GraalVM 25-dev.

    1. Install necessary Linux tools and adjust kernel permissions for performance monitoring.
    2. Package the script example01.sc into a JAR.
    3. Run the JAR using java with sun-misc-unsafe-memory-access=allow enabled, wrapped in perf stat to 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/null
  10. Set up a development environment for jsoniter-scala

    master

    To 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:

    1. Add the upstream remote and fetch tags.
    2. Install system dependencies (Clang, libstdc++, libgc).
    3. 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 16
  11. Run Scala.js benchmarks

    master

    To build and run Scala.js benchmarks, use JDK 17+ to build the jsoniter-scala-benchmarkJS module.

    1. Build the optimized JS: sbt -DassemblyJSBenchmarks -java-home /usr/lib/jvm/jdk-17 +jsoniter-scala-benchmarkJS/fullOptJS.
    2. Open the generated HTML reports in your browser from the jsoniter-scala-benchmark/js directory.
    3. 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