How the Clojure reader turns text into forms, where EDN fits as a data subset, and why `clojure.edn/read-string` is the safer default for config and interchange.
The Clojure reader is the broader mechanism that turns source text into Clojure forms. EDN lives inside that world as a data-oriented subset for configuration and interchange. A lot of beginner confusion comes from collapsing those two ideas into one.
EDN (Extensible Data Notation): A Clojure-friendly data notation that looks like ordinary Clojure literals but intentionally limits itself to data-oriented forms.
That distinction matters in practice:
clojure.edn/read-string is usually the right tool for external dataclojure.core/read-string is not the safe default for config files or user inputThe Clojure reader is the first layer in the language pipeline. It scans characters and produces in-memory forms such as:
For example:
1(+ 1 2 3)
The reader does not execute this expression. It first turns it into a list whose first element is the symbol + and whose remaining elements are numbers. Evaluation happens later.
That “code as data” property is what makes Lisp-style macros possible. But it also means you need to keep the reader and the evaluator conceptually separate.
EDN feels natural in Clojure because its syntax is close to ordinary literals. But it deliberately narrows the surface to data-oriented forms rather than the full range of reader behavior.
Typical EDN values include:
1;; A list
2(1 2 3 4)
3
4;; A vector
5[1 2 3 4]
6
7;; A map
8{:name "Alice" :age 30}
9
10;; A set
11#{1 2 3 4}
12
13;; Nested data
14{:user {:name "Bob" :age 25}
15 :roles [:admin :user]}
EDN is stronger than JSON when the domain benefits from:
It is weaker when the surrounding ecosystem expects JSON everywhere and cross-language tooling convenience matters more than expressiveness.
EDN remains popular in Clojure systems because it balances readability and structure well.
Its main advantages are:
The real benefit is not theoretical elegance. It is that configuration and interchange data can stay close to the way Clojure programs already think about values.
clojure.edn/read-string for DataThe practical rule modern pages should teach is simple:
clojure.edn/read-string for EDN dataclojure.core/read-string for external input1(require '[clojure.edn :as edn])
2
3(def config
4 (edn/read-string
5 "{:database {:host \"localhost\" :port 5432}
6 :features #{:audit :metrics}}"))
7
8(def serialized (pr-str config))
This boundary matters because EDN is intended for data interchange, while the full reader participates in a broader language model.
One of EDN’s most useful features is tagged literals. They let you express specialized data without abandoning a readable text format.
1(defn point-reader [[x y]]
2 {:x x :y y})
3
4(def custom-readers {'point point-reader})
5
6(def point
7 (clojure.edn/read-string
8 {:readers custom-readers}
9 "#point [10 20]"))
10;; => {:x 10 :y 20}
This is powerful, but it is best used with restraint. Tagged literals work well when the tag represents a stable domain concept. They work badly when they become an ad hoc object-construction escape hatch.
The visual below focuses on anatomy rather than process. It labels the pieces readers often confuse: list delimiters, symbols, keywords, vectors, maps, sets, and tagged literals.
What to notice:
(+ 1 2) is a list form, not an already executed computation:name and :roles are keywords, which is one reason EDN feels natural in Clojure config#{...} marks a set#uuid is a tagged literal, which is part of EDN’s extensibility storyOnce those shapes become visually familiar, the reader/EDN distinction gets much easier to reason about.
EDN is a strong fit when the producer and consumer are already comfortable with Clojure-style data and the extra expressiveness is useful.
It is often the better choice for:
JSON is often the better choice when:
EDN is especially common in configuration because it stays readable while still supporting richer value shapes than JSON.
1;; config.edn
2{:database {:host "localhost" :port 5432}
3 :logging {:level :info}
4 :features #{:audit :metrics}}
That is usually enough structure to keep configuration expressive without making it feel like executable code.
clojure.core/read-string is not the right default for external input just because it happens to parse text.For further reading on EDN, visit the EDN Format documentation.