# ol.ron next Fast RON (Readable Object Notation) reader and writer for Clojure ## ol.ron # ol.ron Fast RON (Readable Object Notation) for Clojure. RON keeps the JSON data model while removing punctuation where the meaning is unambiguous. Pretty output is the default. Compact output preserves available member order, and canonical output implements RFC 8785 and I-JSON. [`read-string`](#read-string) and [`read-bytes`](#read-bytes) parse directly into Clojure data. [`write-string`](#write-string), [`write-bytes`](#write-bytes), and [`write`](#write) render Clojure data directly. <<ron--GT-json,`ron->json`>> and <<json--GT-ron,`json->ron`>> preserve number source text outside canonical mode. ## ron->json ```clojure (ron->json ron) (ron->json ron opts) ``` Converts RON text to JSON text. Pretty and compact modes preserve parsed member order and number spelling. Canonical mode applies RFC 8785 and I-JSON. Options: | | | | --- | --- | | key | description | | `:mode` | `:pretty` (default), `:compact`, or `:canonical` | | `:pretty` | Compatibility option used only when `:mode` is absent | | `:max-depth` | Maximum object/array nesting depth (default `1000`) | See also <<json--GT-ron,`json->ron`>>. [source,window=_blank](https://github.com/outskirtslabs/ron-clj/blob/main/src/ol/ron.clj#L76-L95) --- ## json->ron ```clojure (json->ron json) (json->ron json opts) ``` Converts JSON text to RON text. Pretty output is the default and elides non-empty root-object braces. Typed value hooks replace values by path before rendering and do not recurse into a replacement value. Options: | | | | --- | --- | | key | description | | `:mode` | `:pretty` (default), `:compact`, or `:canonical` | | `:pretty` | Compatibility option used only when `:mode` is absent | | `:max-depth` | Maximum object/array nesting depth (default `1000`) | | `:typed-value-hooks` | Path replacements with `:path` and `:replace-with` | See also <<ron--GT-json,`ron->json`>>. [source,window=_blank](https://github.com/outskirtslabs/ron-clj/blob/main/src/ol/ron.clj#L97-L117) --- ## read-string ```clojure (read-string ron) (read-string ron opts) ``` Parses RON text directly into Clojure data. Objects become maps, arrays become vectors, integers become longs or bigints, and decimal/exponent numbers become doubles. Options: | | | | --- | --- | | key | description | | `:key-fn` | Function applied to every object key (default `identity`) | | `:max-depth` | Maximum object/array nesting depth (default `1000`) | The built-in `identity` and `keyword` key functions use cached key paths. See also [`read-bytes`](#read-bytes) and [`write-string`](#write-string). [source,window=_blank](https://github.com/outskirtslabs/ron-clj/blob/main/src/ol/ron.clj#L119-L138) --- ## read-bytes ```clojure (read-bytes input) (read-bytes input opts) ``` Parses a UTF-8 byte array directly into Clojure data. Malformed UTF-8 throws `ol.ron.Ron$ParseException` with a byte offset. Options: | | | | --- | --- | | key | description | | `:key-fn` | Function applied to every object key (default `identity`) | | `:max-depth` | Maximum object/array nesting depth (default `1000`) | See also [`read-string`](#read-string) and [`write-bytes`](#write-bytes). [source,window=_blank](https://github.com/outskirtslabs/ron-clj/blob/main/src/ol/ron.clj#L140-L157) --- ## write-string ```clojure (write-string data) (write-string data opts) ``` Renders Clojure data directly as RON text. Map keys may be strings, keywords, or symbols. Values may be nil, booleans, strings, finite numbers, keywords, symbols, maps, and sequential collections. Options: | | | | --- | --- | | key | description | | `:mode` | `:pretty` (default), `:compact`, or `:canonical` | | `:pretty` | Compatibility option used only when `:mode` is absent | | `:max-depth` | Maximum collection nesting depth (default `1000`) | See also [`write-bytes`](#write-bytes) and [`read-string`](#read-string). [source,window=_blank](https://github.com/outskirtslabs/ron-clj/blob/main/src/ol/ron.clj#L159-L177) --- ## write-bytes ```clojure (write-bytes data) (write-bytes data opts) ``` Renders Clojure data directly as UTF-8 RON bytes. Options: | | | | --- | --- | | key | description | | `:mode` | `:pretty` (default), `:compact`, or `:canonical` | | `:pretty` | Compatibility option used only when `:mode` is absent | | `:max-depth` | Maximum collection nesting depth (default `1000`) | See also [`write`](#write) and [`write-string`](#write-string). [source,window=_blank](https://github.com/outskirtslabs/ron-clj/blob/main/src/ol/ron.clj#L179-L194) --- ## write ```clojure (write data output-stream) (write data output-stream opts) ``` Writes UTF-8 RON to `output-stream`, flushes it, and returns it. The function does not close the caller’s stream. Options: | | | | --- | --- | | key | description | | `:mode` | `:pretty` (default), `:compact`, or `:canonical` | | `:pretty` | Compatibility option used only when `:mode` is absent | | `:max-depth` | Maximum collection nesting depth (default `1000`) | See also [`write-bytes`](#write-bytes). [source,window=_blank](https://github.com/outskirtslabs/ron-clj/blob/main/src/ol/ron.clj#L196-L213) --- ## ron-bytes->json-bytes ```clojure (ron-bytes->json-bytes input) (ron-bytes->json-bytes input opts) ``` Converts UTF-8 RON bytes to UTF-8 JSON bytes. Options: | | | | --- | --- | | key | description | | `:mode` | `:pretty` (default), `:compact`, or `:canonical` | | `:pretty` | Compatibility option used only when `:mode` is absent | | `:max-depth` | Maximum object/array nesting depth (default `1000`) | See also <<ron--GT-json,`ron->json`>>. [source,window=_blank](https://github.com/outskirtslabs/ron-clj/blob/main/src/ol/ron.clj#L215-L230) --- ## json-bytes->ron-bytes ```clojure (json-bytes->ron-bytes input) (json-bytes->ron-bytes input opts) ``` Converts UTF-8 JSON bytes to UTF-8 RON bytes. Options: | | | | --- | --- | | key | description | | `:mode` | `:pretty` (default), `:compact`, or `:canonical` | | `:pretty` | Compatibility option used only when `:mode` is absent | | `:max-depth` | Maximum object/array nesting depth (default `1000`) | | `:typed-value-hooks` | Path replacements with `:path` and `:replace-with` | See also <<json--GT-ron,`json->ron`>>. [source,window=_blank](https://github.com/outskirtslabs/ron-clj/blob/main/src/ol/ron.clj#L232-L251) ## Changelog # Changelog All notable changes to this project will be documented in this file. This project uses [**Break Versioning**](https://www.taoensso.com/break-versioning). ## Unreleased ### Added * Add RON reading and writing for Clojure data, strings, UTF-8 bytes, and output streams. * Add RON and JSON conversion in pretty, compact, and canonical modes. * Add the upstream RON conformance and RFC 8785 test suites. ## Contributing # Contributing Contributions are welcome. Read the [Outskirts Labs Contributing Guide](https://docs.outskirtslabs.com/contributing-guide) before you send a change. ## Set up clj-ron requires JDK 25 and Clojure 1.12 or newer. The Nix development shell includes the required tools. ```bash nix develop bb ci ``` Run `bb ci` before you send a change. It checks formatting, runs clj-kondo, and runs the tests. The Java build uses `--release 25`, `-Xlint:all`, and `-Werror`. ## Test scalar and Vector code Start with a clean Java build: ```bash clojure -T:build clean clojure -T:build compile-java ``` Then run the test suite three ways: ```bash clojure -M:kaocha clojure -J-Dol.ron.vector=false -M:kaocha clojure -J--add-modules=jdk.incubator.vector -M:kaocha ``` The first command tests normal scalar startup without the incubator module. The second command tests the explicit off switch. The last command tests Vector code. Send a change only after all three runs pass. ## Run benchmarks Use a quick run while you work: ```bash bb jmh quick scalar repeated-records read-string write-string-compact bb jmh quick vector long-ascii read-bytes write-bytes-compact ``` Use a full run before you make a performance claim: ```bash bb jmh full scalar bb jmh full vector ``` A quick run uses one fork, two one-second warmups, and three one-second measurements. It selects tiny objects, repeated records, and long ASCII strings. A full run uses two forks, five two-second warmups, and five two-second measurements. It runs every payload in `bench/ol/ron/payloads.clj`. Compare runs from the same JDK, computer, heap size, payload, and output mode. Use full runs for final numbers. Correct RON output takes priority over speed. ### Reference results These results came from OpenJDK 25.0.4. JMH used two forks and the repeated-record payload. | | | | | | --- | --- | --- | --- | | Operation | Previous result | Direct implementation | Allocation reduction | | `read-string` | 4.747 ms, 16.49 MB/op | 3.429 ms, 5.90 MB/op | 64% | | compact `write-string` | 4.335 ms, 8.41 MB/op | 2.858 ms, 0.65 MB/op | 92% | | pretty `write-string` | 6.336 ms, 19.05 MB/op | 3.252 ms, 5.79 MB/op | 70% | For long ASCII strings, Vector byte reading ran 5.7 times faster than scalar reading. Vector byte writing ran 3.0 times faster. Tiny-object results stayed within 5% of scalar speed. The number-array `write-bytes` test returned 49,593 bytes and allocated 49,657 bytes per operation. The stream test allocated 40 bytes per operation. ## Implementation Notes The stream writer uses a pooled 64 KiB buffer. Never store caller data or an output stream in pooled state. Vector support is optional. Scalar code must run when the JVM cannot resolve `jdk.incubator.vector`. Put `VECTOR` first in any short-circuit condition that refers to `VectorScan`. Otherwise, the JVM can load an incubator class before it selects the scalar path. `-XX:+UseCompactObjectHeaders` can reduce allocation on supported JDKs. Callers may enable it, but clj-ron does not require it. `test/ron/` contains the RON v0.3 conformance, RFC 8785, and vocabulary fixtures. Treat these files as upstream test data. Never edit a fixture to hide an implementation bug. When you import a new corpus, update its manifest and run all three test commands. ## Build the documentation Run these commands after you change the README, public API, public docstrings, changelog, security policy, or this file: ```bash clojure -T:build clean clojure -T:build compile-java bb gen-docs ``` The clean build prevents stale source copies in `target/classes` from creating duplicate API entries. ## ol.ron # ol.ron > Fast zero-dependency [RON (Readable Object Notation)](https://github.com/starfederation/ron) reader and writer for Clojure. ![doc](https://img.shields.io/badge/doc-outskirtslabs-orange.svg) ![status: experimental](https://img.shields.io/badge/status-experimental-red.svg) ![alt=nixbot](https://ci.outskirtslabs.com/repos/github/outskirtslabs/ron-clj/badge.svg) RON is a data interchange format like JSON. It represents null, booleans, numbers, strings, arrays, and objects. RON keeps the JSON data model but removes punctuation when the meaning is clear. Strings can be bare, commas are optional, and a root object’s braces can be omitted. `ol.ron` reads and writes Clojure data directly. It also converts RON and JSON text or UTF-8 bytes. Pretty, compact, and canonical output modes are available. In canonical mode, data writers produce canonical RON; RON-to-JSON conversion produces RFC 8785 canonical JSON and enforces I-JSON. This is Readable Object Notation, not [Rusty Object Notation](https://github.com/ron-rs/ron). Project status: **[Experimental](https://docs.outskirtslabs.com/open-source-vital-signs#experimental)**. It tracks the evolving RON specification and currently passes the v0.3.0 conformance suite. ## Installation A Clojars release is not available yet. Use a git dependency: ```clojure ;; deps.edn {:deps {com.outskirtslabs/ron-clj {:git/url "https://github.com/outskirtslabs/ron-clj.git" :git/sha "a47cff6a6a5657df74b25e63ac0deb1b7ebd04e3"}}} ``` `ol.ron` requires JDK 25 and Clojure 1.12 or newer, but has no other dependencies. The scalar implementation works without optional JDK modules. To accelerate long clean ASCII spans, opt in to the Vector API: ```bash clojure -J--add-modules=jdk.incubator.vector -M ``` Set `-Dol.ron.vector=false` to disable Vector code in a JVM that includes the module. Note that vector acceleration does not materially improve typical short RON tokens, so don’t bend over backward to enable it. ## Quick Start ```clojure (require '[ol.ron :as ron]) (def text (ron/write-string {:name "Ada" :roles ["admin" "writer"] :active true})) text ;; => "name Ada\nroles [admin writer]\nactive true\n" (ron/read-string text {:key-fn keyword}) ;; => {:name "Ada", :roles ["admin" "writer"], :active true} (ron/ron->json text {:mode :compact}) ;; => "{\"name\":\"Ada\",\"roles\":[\"admin\",\"writer\"],\"active\":true}" ``` ## Core APIs * `read-string` and `write-string` parse and render Clojure data as RON text. * `read-bytes` and `write-bytes` parse and render Clojure data as UTF-8 byte arrays. * `write` renders UTF-8 RON to an output stream. It flushes and returns the stream but does not close it. * `ron->json` and `json->ron` convert text. `ron-bytes->json-bytes` and `json-bytes->ron-bytes` are their UTF-8 byte-array equivalents. ## Documentation * [Docs](https://docs.outskirtslabs.com/ol.ron/next/) * [API Reference](https://docs.outskirtslabs.com/ol.ron/next/api) * [Support via GitHub Issues](https://github.com/outskirtslabs/ron-clj/issues) ## Output Modes * `:pretty` writes multiline output and is the default. * `:compact` writes one line and preserves member order when available. * `:canonical` makes data writers and `json->ron` emit canonical RON; `ron->json` emits RFC 8785 canonical JSON. Ordinary Clojure hash maps do not promise source order. Use a collection with meaningful iteration order or canonical mode when order matters. ## Contributing See the [contributing guide](https://github.com/outskirtslabs/ron-clj/blob/main/CONTRIBUTING.adoc) for development, testing, benchmarking, and documentation commands. ## License Copyright © 2026 Casey Link <casey@outskirtslabs.com>. Distributed under the [MIT License](https://github.com/outskirtslabs/ron-clj/blob/main/LICENSE). ## Security # Security Report vulnerabilities through [GitHub Security Advisories](https://github.com/outskirtslabs/ron-clj/security/advisories). For general policy and support expectations, see the [Outskirts Labs Security Policy](https://docs.outskirtslabs.com/security-policy).