Source code GoDoc license

go-edn is a fully compliant Go implementation of the EDN format, designed to be very similar to encoding/json.

import "olympos.io/encoding/edn"

To use it, import the import path above with your favourite dependency management tool and refer to it in the files where you want to use it.

For an introduction, see introduction.md.

More/Better Data Types

EDN provides better data types out of the box. Most, if not all of us, use timestamps, and EDN has its own type for that built in:

#inst "2015-09-28T13:20:19.570-00:00"

This makes handling timestamps straightforward, as there is only one possible format for timestamps.

EDN also has sets built in:

#{"foo" "bar" "baz"}

By using sets, you can explicitly say that the order of the elements does not matter, and that all the elements will be distinct.

Finally, EDN has “true” maps, which can use anything as a key:

{:users {1 {:name "Luis"}
         2 {:name "Henry"}}
 :objects {[0 3] {:type :gold, :value 100}
           [2 2] {:type :shovel, :durability 0.7}}}


The E in EDN stands for extensible. This means you can define your own EDN datatypes, instead of having your consumers “magically” find out that some values should be treated in a special manner.

Consider, for example, the need to manage time durations. If we need to register events with timeouts, we can do so by adding a new tag reader:

func init() {
    edn.AddTagFn("duration", time.ParseDuration)

type ProcessAction struct {
    Timeout time.Duration
    Element int

func parseAction() {
    input := `
{:timeout #duration "30s"
 :element 10213}`
    var pa ProcessAction
    err := edn.UnmarshalString(input, &pa)
    if err != nil {
        // ...

Through these tagged literals, go-edn is able to deserialise data into interfaces, as long as the tagged literal satisfy the interface it is deserialised into:

type Person struct {
    ID int

func (p *Person) Notify() {
    log.Printf("Notified person %d", p.ID)

type Group struct {
    ID int

func (g *Group) Notify() {
    log.Printf("Notified group %d", g.ID)

func init() {
    edn.AddTagStruct("myapp/person", &Person{})
    edn.AddTagStruct("myapp/group", &Group{})

type Notifier interface {

func test() {
    var subscribers []Notifier
    input := `
[#myapp/person {:id 10}
 #myapp/group {:id 1}
 #myapp/person {:id 1}]`
    err := edn.UnmarshalString(input, &subscribers)
    if err != nil {
        // handle error
    for _, subscriber := range subscribers {


If you have used JSON for configuration files, you’ve probably noticed that there are many creative ways to encode comments into them. EDN supports comments out of the box, which means you don’t have to break the original spec or add dummy values to include comments:

;; Configuration options for myapp
:myapp {;; Myapp is really available on 443 through reverse proxying done by
        ;; nginx, to avoid handling SSL ourselves. 3000 is blocked to the
        ;; public via iptables.
        :port 3000
        ;; Timestamp to put on elements that will be cached forever by HTTP
        ;; caches. If not set, it is placed one year ahead of the current time.
        :forever-date #inst "2032-01-01T12:20:50.52Z"
        ;; How many goroutines we should delegate to processing data.
        :process-pool 5}


go-edn is stable and is is guaranteed to be backwards compatible.