# Cheshire, the JSON codec that came out of a fork in the road

> Cheshire is a Clojure JSON library built on Jackson, with support for SMILE, custom encoders and extra Clojure types such as Date, UUID, Set and Symbol. It exists because clojure-json had features but was slow, while clj-json was fast but featureless, and it is a built-in library in Babashka.

**dakrone/cheshire** — Clojure JSON and JSON SMILE (binary json format) encoding/decoding

- Repository: https://github.com/dakrone/cheshire
- Website: https://github.com/dakrone/cheshire
- Stars: 1,562 · Forks: 156
- Language: Clojure
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/dakrone-cheshire

## It exists because the two obvious choices each failed differently

Cheshire is described as fast JSON encoding, based off clj-json and clojure-json, with additional features like Date, UUID, Set and Symbol encoding and SMILE support. The Why section of the README states the tension plainly. clojure-json had really nice features such as custom encoders, but was slow. clj-json had no features, but was fast. Cheshire's position is the middle one: it encodes JSON fast, with added support for more types and the ability to use custom encoders.

That framing is worth pausing on, because it tells you the design constraint. The speed comes from Jackson, so the question is not whether Cheshire can be fast but whether the library sitting on top of Jackson adds overhead. Everything else in the library is a decision about what sits between you and the Jackson API.

The implementation is a Leiningen project, with project.clj at the repository root, a src/ directory for the code, a test/ directory, and a benchmarks/ directory that shows speed was treated as a first class concern rather than an afterthought. There is also a nvd-helper/ directory, which pairs with the vulnerability scan workflow badge in the README, and a.clj-kondo/ directory for the linter configuration. The documentation is published on cljdoc at the cheshire/cheshire path, and the library is distributed through Clojars, with a version badge for it in the README header.

The most telling badge is the Babashka one. Cheshire is a built-in library in the Babashka project, which means every Babashka script already has a JSON codec available without adding a dependency.

## Encoding options cover dates, unicode, key munging and pretty printing

The encoding API is a handful of functions, and the README demonstrates each one with a comment and an example result. The core is generate-string for a string, generate-stream to write to a writer, and generate-smile for the binary SMILE format:

```clojure
(generate-string {:foo "bar" :baz 5})
(generate-stream {:foo "bar" :baz 5} (clojure.java.io/writer "/tmp/foo"))
(generate-smile {:foo "bar" :baz 5})
```

Dates are handled with a default format of yyyy-MM-dd'T'HH:mm:ss'Z', and the format is overridable per call:

```clojure
(generate-string {:foo "bar" :baz (java.util.Date. 0)})
(generate-string {:baz (java.util.Date. 0)} {:date-format "yyyy-MM-dd"})
```

Pretty printing is a boolean option, and the README shows the result, with the space before the colon that Jackson produces by default:

```clojure
(generate-string {:foo "bar" :baz {:eggplant [1 2 3]}} {:pretty true})
```

There is also a way to replace the pretty printer itself, for when Jackson's default layout is not what you want. You build a printer from the default options and pass it through the :pretty option:

```clojure
(let [my-pretty-printer (create-pretty-printer
                          (assoc default-pretty-print-options
                                 :indent-arrays? true))]
  (generate-string {:foo [1 2 3]} {:pretty my-pretty-printer}))
```

The default-pretty-print-options map is the list of knobs you can change, and the README points at it directly.

Two smaller options are easy to miss. escape-non-ascii writes the pound sign as a unicode escape, and the README shows the result as "{\"foo\":\"It costs \\u00A3100\"}", which matters when a consumer is strict about ASCII output. And a key-fn option rewrites the keys on the way out, so a custom function that uppercases the name of each key produces "{\"FOO\":\"bar\"}". That is a different feature from custom encoders, because it changes the map key rather than how a value is written.

## Decoding gives you keywords or strings, and lets you choose the collection type

On the reading side, parse-string is the entry point, and the second argument decides what a JSON object key becomes. With it omitted or false you get strings, and with true you get keywords:

```clojure
(parse-string "{\"foo\":\"bar\"}")
;; => {"foo" "bar"}

(parse-string "{\"foo\":\"bar\"}" true)
;; => {:foo "bar"}
```

A custom function can be passed instead of the boolean, so you can normalise keys however you like, for example uppercasing them into keywords. Top level strings are valid JSON too, so parse-string on a quoted value returns that string rather than an error.

The keyword option is available on the other readers as well, which is the point of the design. There is parse-smile for a byte array, parse-stream for a reader, parsed-seq for a lazily parsed stream, and parsed-smile-seq for a lazy SMILE stream. The lazy variants matter for large payloads, since a whole document does not have to sit in memory as one map.

The most interesting decoding option arrived in 2.0.4 and up, where you can pass a function that decides what kind of type to return, keyed on the field name. The README's example decodes one field into a set and another into a vector:

```clojure
(decode "{\"myarray\":[2,3,3,2],\"myset\":[1,2,2,1]}" true
        (fn [field-name]
          (if (= field-name "myset")
            #{}
            [])))
;; => {:myarray [2 3 3 2], :myset #{1 2}}
```

The README is explicit about the constraint: the type must be transient-able, so either a set or a vector. That restriction is the price of the feature, and it is the difference between a JSON array that always becomes a list and one that can become a set when duplicates do not matter to your data.

## Custom encoders moved into the core namespace, and decoding stayed yours

Custom encoding is how you teach Cheshire about a type it does not know, typically a Java class from another library. The API is a small set of functions in the cheshire.generate namespace: add-encoder, encode-str and remove-encoder, and the README shows registering a handler for java.awt.Color that writes a string:

```clojure
(add-encoder java.awt.Color
             (fn [c jsonGenerator]
               (.writeString jsonGenerator (str c))))

(add-encoder java.net.URL encode-str)

(encode (java.awt.Color. 1 2 3))
;; => "java.awt.Color[r=1,g=2,b=3]"

(remove-encoder java.awt.Color)
```

There is a set of built-in encoders for the common cases, listed in the README as a comment block: encode-nil, encode-number, encode-seq, encode-date, encode-bool, encode-named, encode-map, encode-symbol and encode-ratio. The presence of encode-symbol and encode-ratio is one of the differences from a plain Jackson setup, since a Clojure symbol or ratio has no obvious JSON counterpart and needs a rule.

The namespace story has changed over time and is easy to trip over in older code. Custom encoding was supported from 2.0.0, and the README notes that from 5.0.0 onwards it moved into the core namespace, so there is no longer a need to require a different namespace depending on what you want to use. The namespace cheshire.custom has been deprecated in version 5.0.0, and custom and core encoding were combined in Cheshire 5.0.0.

The other half of the contract is the one people forget. The README states that decoding remains the same, and that you are responsible for doing custom decoding. So an encoder you register writes that type out, but the matching read path is yours to write. A value that came out as a custom string will come back as a plain string unless you handle it, and the library will not raise an error to tell you.

## Aliases, versions and what the changelog is for

A set of aliases keeps older code readable. The README lists them plainly: encode maps to generate-string, encode-stream to generate-stream, encode-smile to generate-smile, decode to parse-string, decode-stream to parse-stream, and decode-smile to parse-smile. So a project that started with the decode and encode names can keep using them, and a project moving toward the newer generate and parse naming can switch gradually.

The version story is documented in the README's usage snippet rather than in a release feed, and it is unusually explicit about the pairing. The dependency block shows Cheshire 6.2.0, and a comment underneath notes that Cheshire v6.2.0 uses Jackson 2.21.1. Since Cheshire's whole claim is that it is fast, and Jackson is where the speed comes from, that pairing is the thing to check when you see a transitive Jackson upgrade in your own dependency tree.

The repository has no GitHub releases, so ChangeLog.md at the root is the version record. The default branch is master, the last push was on 2026-07-25, and the library is MIT licensed with the LICENSE file at the root.

For a sense of who relies on this, the README gives two strong signals. Cheshire is a built-in library in Babashka, and the README links the Clojars project page and the cljdoc documentation. The Clojure ecosystem is smaller than most, and a library that is both a Clojars package and a Babashka builtin has a long tail of users who will never read its source, which is a good reason to keep the API surface as small as it is here.

## Conclusion

Cheshire suits Clojure projects that already have Jackson on the classpath through other libraries, that want to opt into keyword keys, and that need SMILE or custom encoders without hand rolling them, and it suits anyone running Babashka, where the library is built in. It is the wrong choice if you want a dependency free parser, since Cheshire pulls in Jackson and its own project.clj, and it is the wrong choice if you need a fully custom decoding path, because the README is explicit that custom decoding is your responsibility. Before adopting it, check the version you are pinning, since the README shows Cheshire 6.2.0 running on Jackson 2.21.1, and read the ChangeLog.md in the repository root, since the project has no GitHub releases and the changelog is the only version record. The last push was on 2026-07-25.

## FAQ

### What is Cheshire and why was it written?

Cheshire is a fast JSON and SMILE codec for Clojure built on Jackson, with extra support for Date, UUID, Set and Symbol types and custom encoders. The README says it exists because clojure-json had custom encoder features but was slow, while clj-json was fast but had no features.

### How do I get keyword keys when parsing JSON with Cheshire?

Pass true as the second argument to parse-string, which returns keyword keys instead of strings. You can also pass a custom function to transform keys, and the same keyword option works with parse-smile, parse-stream, parsed-seq and parsed-smile-seq.

### How do I register a custom encoder in Cheshire?

Use add-encoder from the cheshire.generate namespace with a class and a function that writes to the JSON generator, and remove-encoder to undo it. From version 5.0.0 this lives in the core namespace, and the separate cheshire.custom namespace has been deprecated.

### Does Cheshire support decoding into sets?

Yes. Since 2.0.4 you can pass a function to decode that decides the collection type per field name, returning either #{} or [] since the type must be transient-able. A small example in the README decodes one field into a vector and another into a set.

### Which version of Cheshire should I use with Jackson?

The README's dependency snippet shows Cheshire 6.2.0 using Jackson 2.21.1. Since the project has no GitHub releases, the ChangeLog.md in the repository root is the version record to check when you upgrade.

## Sources

- [dakrone/cheshire on GitHub](https://github.com/dakrone/cheshire)
- [Issues](https://github.com/dakrone/cheshire/issues)
- [License: MIT](https://github.com/dakrone/cheshire/blob/master/LICENSE)
- [Project website](https://github.com/dakrone/cheshire)
- [README](https://github.com/dakrone/cheshire/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dakrone-cheshire
