Metosin

Faster JSON processing with jsonista

We are happy to announce the initial release of our JSON encoding and decoding library for Clojure, jsonista 0.1.0. It’s fast and it has explicit configuration – no global mutable state!

Yet another JSON library?!

Yes, you read that right: we’ve created yet another JSON library for Clojure. There are a couple of them, but probably the most popular one is Cheshire. It’s fast and full of features, so why did we create a new library?

A year ago, Tommi started to focus on the performance of the Clojure web stack and as a result, we got Muuntaja, a ground-up rewrite of ring-middleware-format. Muuntaja is a Ring middleware and Pedestal interceptor library for parsing and decoding data in HTTP requests and responses. It handles the content type negotiation, but it defers to libraries like Cheshire and transit-clj for the actual serialization and deserialization.

Muuntaja is much faster than it’s predecessor, but this got us thinking. Could we make the JSON processing itself go faster?

Tommi suggested that we build on jackson-databind. It’s a high-level library for mapping Java objects to JSON. This suggestion didn’t make any sense to me: since Cheshire is built on the low-level jackson-core, there’s no way using databind would be faster than that. To demonstrate Tommi how wrong he was, I whipped up a quick implementation and some benchmarks. Guess what happened?

The databind version was faster. Clearly my intuition was off! Here are the results from a more recent benchmark:

Benchmarking is hard and likely we made mistakes. Together with Tommi and Kalle, we decided to put the code in a library anyway. That’s how we got jsonista.

What makes it faster than Cheshire? We believe these are the main reasons:

  • Jsonista pushes as much work as possible to Jackson and to Java, whereas Cheshire's performance-critical parts are in Clojure.
  • Cheshire's big functions and use of definline prevent JVM inlining. Jsonista's methods are small.
  • Unlike Jsonista, Cheshire uses dynamic variables. They always come with a performance penalty.

Jackson's authors have taken care to make Jackson fast and Jsonista tries to make full use of that.

Usage

The coordinates are [metosin/jsonista "0.1.0"]. The basic operations are straightforward:

(require '[jsonista.core :as j])

(j/write-value-as-string {"hello" 1})
;; => "{\"hello\":1}"

(j/read-value *1)
;; => {"hello" 1}

Muuntaja has built-in support for jsonista. You can enable it in your Muuntaja configuration like this:

(require '[muuntaja.format.jsonista :as json-format])

(def m
  (muuntaja/create
    (-> muuntaja/default-options
        json-format/with-json-format)))

For more information about configuration and custom encoders, see GitHub and the API reference. Once you have tried it out, please let us know how it worked for you!