Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Lisp Dialects and Their JSON Formatting Capabilities

If you need to parse, generate, or pretty-print JSON from Lisp code, the answer depends heavily on the dialect. Common Lisp usually relies on third-party libraries and lets you choose your object/array mapping. Clojure aligns very naturally with JSON through maps and vectors. Racket ships an official JSON library, but it expects values that satisfy its stricter jsexpr? data model.

For most real projects, JSON support is not the hard part. The hard part is choosing the right representation for keys, arrays, numbers, and null. That is where dialects differ, and those differences affect whether your code feels pleasant or constantly full of conversion helpers.

Quick Answer

All major Lisp families can handle JSON today, but they do not do it the same way. If you want the shortest path from JSON to native data and back, Clojure tends to be the smoothest fit. If you want maximum representation control, Common Lisp gives you more knobs. If you want batteries included in a Scheme-family language, Racket is the clearest current example.

DialectCurrent Practical ChoiceDefault JSON ShapeWhat To Watch
Common Lispcl-json for documented parsing and serializationObjects are alists by default; arrays are listsBe explicit about key mapping and false versus null
Clojureclojure.data.json or cheshireObjects map cleanly to maps; arrays to vectorsChoose whether keys stay strings or become keywords
RacketBuilt-in json libraryObjects are usually symbol-keyed hashes; arrays are listsOnly values matching jsexpr? can be written

What Changes Between Dialects

  • Object keys: Clojure can keep JSON keys as strings or keywordize them on read. Common Lisp often maps keys to symbols and may normalize names. Racket's JSON expressions typically use symbol keys in hashes.
  • Arrays: Clojure defaults to vectors, while Common Lisp and Racket often use lists unless you opt into a different representation.
  • Null and booleans: Clojure keeps nil and false distinct. Racket keeps JSON null distinct from #f. With Common Lisp's default CL-JSON semantics, false and null can both collapse to NIL, which matters if your API distinguishes them.
  • Numbers: Clojure's official JSON library can preserve decimal values as BigDecimal via :bigdec true. Common Lisp and Racket handle numbers cleanly, but you still need to watch precision rules and how your application expects to consume them.
  • Large payloads: Streaming APIs exist, but they are library-specific. If you work with very large documents, avoid assuming that all examples using strings scale directly to production.

This is also why a standalone formatter is useful even when your language already has a JSON library. It lets you validate sample payloads, inspect nesting, and normalize whitespace before you lock a representation into code.

Common Lisp

Common Lisp has multiple JSON libraries, but cl-json remains a well-documented reference point because its behavior is explicit. By default it decodes JSON objects using Lisp-friendly key names and list structures, which is flexible but not always what you want for random access or strict schema matching.

Parsing with cl-json

(ql:quickload "cl-json")

(let ((payload "{\"userId\":42,\"roles\":[\"admin\",\"editor\"],\"active\":true}"))
  (json:decode-json-from-string payload))

;; => ((:USER-ID . 42) (:ROLES "admin" "editor") (:ACTIVE . T))

Serializing back to JSON

(json:encode-json-alist-to-string
 '((:user-id . 42)
   (:active . t)
   (:roles . ("admin" "editor"))))

;; => "{\"userId\":42,\"active\":true,\"roles\":[\"admin\",\"editor\"]}"

The practical question in Common Lisp is not "can it do JSON?" but "which semantics do I want?" CL-JSON documents configurable identifier conversion, strict decoding, and alternate decoder semantics that can produce vectors instead of lists. That makes it capable, but it also means you should make the mapping explicit instead of relying on defaults that may surprise the next person reading the code.

Clojure

Clojure is usually the least awkward Lisp dialect for JSON because its core data structures already look like JSON: maps, vectors, strings, booleans, and nil. The current official API in clojure.data.json centers on read-str, read, write-str, and write.

Reading and pretty-printing with clojure.data.json

(require '[clojure.data.json :as json])

(def payload
  (json/read-str
    "{\"service\":\"billing\",\"price\":1200.50,\"enabled\":true}"
    :key-fn keyword
    :bigdec true))

;; => {:service "billing" :price 1200.50M :enabled true}

(json/write-str payload :indent true)
;; => pretty-printed JSON string

One worthwhile current-detail check: older examples on the web still use read-json and write-json, but the library's current docs mark those as deprecated. If you are updating old code or blog content, this is usually the first thing worth correcting.

In day-to-day Clojure work, teams often choose between the official library and cheshire. The official library is straightforward and dependency-light. Cheshire is a common choice when you want Jackson underneath, custom encoders, stream-based parsing, lazy parsing via parsed-seq, or easy pretty-printing with generate-string.

Racket and Scheme-Family Languages

Scheme is fragmented enough that generic advice is often too vague to help. Racket is the best concrete example because it ships an official json library with documented read/write and string conversion functions. Its model is slightly stricter than Clojure's, which is useful once you understand the rules.

Using Racket's built-in json module

(require json)

(define payload
  (string->jsexpr "{\"city\":\"London\",\"visitors\":9000000,\"open\":true}"))

;; => '#hasheq((city . "London") (visitors . 9000000) (open . #t))

(jsexpr->string
  #hasheq((city . "London") (tags . ("capital" "uk")))
  #:indent 2)

;; => pretty-printed JSON string

The important Racket-specific caveat is that JSON output must satisfy jsexpr?. In practice that means arrays are normally lists, not vectors, and object keys in hashes are symbols, not arbitrary strings. If your program naturally builds vectors or string-keyed hashes, convert them before writing JSON.

Racket also keeps JSON null separate from booleans by representing it as 'null by default, which is often more convenient than the ambiguity some Common Lisp defaults create.

Which Dialect Feels Best For JSON?

  • Choose Clojure if you want the lowest impedance mismatch and the clearest path from JSON to idiomatic data.
  • Choose Common Lisp if you want flexible representation control and do not mind deciding the mapping policy yourself.
  • Choose Racket if you want a documented standard library solution and you are comfortable shaping data to fit jsexpr?.

That comparison is an inference from each dialect's default mappings and official APIs, not a universal rule. Performance, ecosystem fit, and the shape of your existing application can matter more than the JSON API alone.

Troubleshooting Checklist

  • If keys mysteriously change, check whether your library is keywordizing, symbolizing, or renaming them.
  • If arrays come back in the wrong shape, confirm whether your dialect defaults to lists or vectors.
  • If null and false behave strangely, test them explicitly before trusting default conversions.
  • If decimals lose precision, look for BigDecimal or exact-number options in your JSON reader.
  • If output is valid but hard to inspect, pretty-print it before debugging application logic.

Bottom Line

Lisp dialects are fully capable of working with JSON, but the best workflow depends on how closely each dialect's native data model matches the JSON you exchange. Clojure is usually the smoothest. Common Lisp is the most configurable. Racket is clear and capable once you respect its JSON-expression rules. If you are comparing sample payloads between dialects, format them first, then make the representation choice explicit in code.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool