edn

package module
v0.0.0-...-d3554ca Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2020 License: BSD-3-Clause Imports: 18 Imported by: 44

README

Go implementation of EDN, extensible data notation

GoDoc

go-edn is a Golang library to read and write EDN (extensible data notation), a subset of Clojure used for transferring data between applications, much like JSON or XML. EDN is also a very good language for configuration files, much like a JSON-like version of YAML.

This library is heavily influenced by the JSON library that ships with Go, and people familiar with that package should know the basics of how this library works. In fact, this should be close to a drop-in replacement for the encoding/json package if you only use basic functionality.

This implementation is complete, stable, and presumably also bug free. This is why you don't see any changes in the repository.

If you wonder why you should (or should not) use EDN, you can have a look at the why document.

Installation and Usage

The import path for the package is olympos.io/encoding/edn

To install it, run:

go get olympos.io/encoding/edn

To use it in your project, you import olympos.io/encoding/edn and refer to it as edn like this:

import "olympos.io/encoding/edn"

//...

edn.DoStuff()

The previous import path of this library was gopkg.in/edn.v1, which is still permanently supported.

Quickstart

You can follow http://blog.golang.org/json-and-go and replace every occurence of JSON with EDN (and the JSON data with EDN data), and the text makes almost perfect sense. The only caveat is that, since EDN is more general than JSON, go-edn stores arbitrary maps on the form map[interface{}]interface{}.

go-edn also ships with keywords, symbols and tags as types.

For a longer introduction on how to use the library, see introduction.md. If you're familiar with the JSON package, then the API Documentation might be the only thing you need.

Example Usage

Say you want to describe your pet forum's users as EDN. They have the following types:

type Animal struct {
	Name string
	Type string `edn:"kind"`
}

type Person struct {
	Name      string
	Birthyear int `edn:"born"`
	Pets      []Animal
}

With go-edn, we can do as follows to read and write these types:

import "olympos.io/encoding/edn"

//...


func ReturnData() (Person, error) {
	data := `{:name "Hans",
              :born 1970,
              :pets [{:name "Cap'n Jack" :kind "Sparrow"}
                     {:name "Freddy" :kind "Cockatiel"}]}`
	var user Person
	err := edn.Unmarshal([]byte(data), &user)
	// user '==' Person{"Hans", 1970,
	//             []Animal{{"Cap'n Jack", "Sparrow"}, {"Freddy", "Cockatiel"}}}
	return user, err
}

If you want to write that user again, just Marshal it:

bs, err := edn.Marshal(user)

Dependencies

go-edn has no external dependencies, except the default Go library. However, as it depends on math/big.Float, go-edn requires Go 1.5 or higher.

License

Copyright © 2015-2019 Jean Niklas L'orange and contributors

Distributed under the BSD 3-clause license, which is available in the file LICENSE.

Documentation

Overview

Package edn implements encoding and decoding of EDN values as defined in https://github.com/edn-format/edn. For a full introduction on how to use go-edn, see https://github.com/go-edn/edn/blob/v1/docs/introduction.md. Fully self-contained examples of go-edn can be found at https://github.com/go-edn/edn/tree/v1/examples.

Note that the small examples in this package is not checking errors as persively as you should do when you use this package. This is done because I'd like the examples to be easily readable and understandable. The bigger examples provide proper error handling.

Example (ArbitraryKeys)

EDN, in contrast to JSON, supports arbitrary values as keys.

package main

import (
	"fmt"

	"olympos.io/encoding/edn"
)

type Point3 struct {
	X, Y, Z int64
}

type Unit struct {
	Type edn.Keyword
	HP   int
}

// EDN, in contrast to JSON, supports arbitrary values as keys.
func main() {
	input := `{{:x 1 :y 2 :z 3}    "Greybeard"
             {:y 10 :x 1 :z -10} "Blackwind"}`

	var locStarships map[Point3]string
	err := edn.UnmarshalString(input, &locStarships)
	if err != nil {
		panic(err)
	}

	p := Point3{1, 10, -10}

	fmt.Printf("Starship at location %v is %s\n", p, locStarships[p])

	input = `{[0 2]   {:type :scout :hp 55}
            [-3 10] {:type :villager :hp 25}
            [5 5]   {:type :bowman :hp 32}
            [5 6]   {:type :bowman :hp 29}}`

	var locUnits map[[2]int]Unit
	err = edn.UnmarshalString(input, &locUnits)
	if err != nil {
		panic(err)
	}

	loc := [2]int{5, 5}

	fmt.Printf("Unit at location %v is %+v\n", loc, locUnits[loc])

}
Output:

Starship at location {1 10 -10} is Blackwind
Unit at location [5 5] is {Type::bowman HP:32}
Example (EnumsAndSets)

This example shows how one can implement enums and sets, and how to support multiple different forms for a specific value type. The set implemented here supports the notation `:all` for all values.

package main

import (
	"fmt"

	"olympos.io/encoding/edn"
)

type UserOption edn.Keyword

type UnknownUserOptionError UserOption

func (err UnknownUserOptionError) Error() string {
	return fmt.Sprintf("Unknown user option %s", edn.Keyword(err))
}

const (
	UnknownOption = UserOption("")
	ShowEmail     = UserOption("show-email")
	Notifications = UserOption("notifications")
	DailyEmail    = UserOption("daily-email")
	RememberMe    = UserOption("remember-me")
)

func ListUserOptions() []UserOption {
	return []UserOption{
		ShowEmail,
		Notifications,
		DailyEmail,
		RememberMe,
	}
}

func (uo *UserOption) UnmarshalEDN(bs []byte) error {
	var kw edn.Keyword
	err := edn.Unmarshal(bs, &kw)
	if err != nil {
		return err
	}
	opt := UserOption(kw)
	switch opt {
	case ShowEmail, Notifications, DailyEmail, RememberMe:
		*uo = opt
		return nil
	default:
		return UnknownUserOptionError(opt)
	}
}

type UserOptions map[UserOption]bool

func (opts *UserOptions) UnmarshalEDN(bs []byte) error {
	var kw edn.Keyword
	// try to decode into keyword first
	err := edn.Unmarshal(bs, &kw)
	if err == nil && kw == edn.Keyword("all") {
		// Put all options into the user option map
		*opts = UserOptions(make(map[UserOption]bool))
		for _, opt := range ListUserOptions() {
			(*opts)[opt] = true
		}
		return nil
	}
	// then try to decode into user map
	var rawOpts map[UserOption]bool
	err = edn.Unmarshal(bs, &rawOpts)
	*opts = UserOptions(rawOpts)
	return err
}

// This example shows how one can implement enums and sets, and how to support
// multiple different forms for a specific value type. The set implemented here
// supports the notation `:all` for all values.
func main() {
	inputs := []string{
		"#{:show-email :notifications}",
		"#{:notifications :show-email :remember-me}",
		":all",
		"#{:doot-doot}",
		":none",
		"#{} ;; no options",
	}
	for _, input := range inputs {
		var opts UserOptions
		err := edn.UnmarshalString(input, &opts)
		if err != nil {
			fmt.Println(err)
			// Do proper error handling here if something fails
			continue
		}
		// Cannot print out a map, as its ordering is nondeterministic.
		fmt.Printf("show email? %t, notifications? %t, daily email? %t, remember me? %t\n",
			opts[ShowEmail], opts[Notifications], opts[DailyEmail], opts[RememberMe])
	}

}
Output:

show email? true, notifications? true, daily email? false, remember me? false
show email? true, notifications? true, daily email? false, remember me? true
show email? true, notifications? true, daily email? true, remember me? true
Unknown user option :doot-doot
edn: cannot unmarshal keyword into Go value of type map[edn_test.UserOption]bool
show email? false, notifications? false, daily email? false, remember me? false
Example (PolymorphicTags)

This example shows how to read and write basic EDN tags, and how this can be utilised: In contrast to encoding/json, you can read in data where you only know that the input satisfies some sort of interface, provided the value is tagged.

package main

import (
	"fmt"
	"strings"

	"olympos.io/encoding/edn"
)

type Notifiable interface {
	Notify()
}

type User struct {
	Username string
}

func (u User) MarshalEDN() ([]byte, error) {
	return edn.Marshal(edn.Tag{"myapp/user", u.Username})
}

func (u *User) Notify() {
	fmt.Printf("Notified user %s.\n", u.Username)
}

type Group struct {
	GroupID int
}

func (g Group) MarshalEDN() ([]byte, error) {
	return edn.Marshal(edn.Tag{"myapp/group", g.GroupID})
}

func (g *Group) Notify() {
	fmt.Printf("Notified group with id %d.\n", g.GroupID)
}

var notifyTagMap edn.TagMap

// We use a tagMap to avoid adding these values to the entire system.
func init() {
	err := notifyTagMap.AddTagFn("myapp/user", func(s string) (*User, error) {
		return &User{s}, nil
	})
	if err != nil {
		panic(err)
	}

	err = notifyTagMap.AddTagFn("myapp/group", func(id int) (*Group, error) {
		return &Group{id}, nil
	})
	if err != nil {
		panic(err)
	}
}

// This example shows how to read and write basic EDN tags, and how this can be
// utilised: In contrast to encoding/json, you can read in data where you only
// know that the input satisfies some sort of interface, provided the value is
// tagged.
func main() {
	input := `[#myapp/user "eugeness"
             #myapp/group 10
             #myapp/user "jeannikl"
             #myapp/user "jeremiah"
             #myapp/group 100]`

	rdr := strings.NewReader(input)
	dec := edn.NewDecoder(rdr)
	dec.UseTagMap(&notifyTagMap)

	var toNotify []Notifiable
	err := dec.Decode(&toNotify)
	if err != nil {
		panic(err)
	}
	for _, notify := range toNotify {
		notify.Notify()
	}

	// Print out the values as well
	out, err := edn.Marshal(toNotify)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(out))
}
Output:

Notified user eugeness.
Notified group with id 10.
Notified user jeannikl.
Notified user jeremiah.
Notified group with id 100.
[#myapp/user"eugeness" #myapp/group 10 #myapp/user"jeannikl" #myapp/user"jeremiah" #myapp/group 100]
Example (Streaming)

This example shows how one can do streaming with the decoder, and how to properly know when the stream has no elements left.

package main

import (
	"fmt"
	"io"
	"strings"

	"olympos.io/encoding/edn"
)

// Typically, you'd also include timestamps here. Imagine that they are here.
type LogEntry struct {
	Level       edn.Keyword
	Message     string `edn:"msg"`
	Environment string `edn:"env"`
	Service     string
}

// This example shows how one can do streaming with the decoder, and how to
// properly know when the stream has no elements left.
func main() {
	const input = `
{:level :debug :msg "1 < 2 ? true" :env "dev" :service "comparer"}
{:level :warn :msg "slow response time from 127.0.1.39" :env "prod" :service "worker 10"}
{:level :warn :msg "worker 8 has been unavailable for 30s" :env "prod" :service "gateway"}
{:level :info :msg "new processing request: what.png" :env "prod" :service "gateway"}
{:level :debug :msg "1 < nil? error" :env "dev" :service "comparer"}
{:level :warn :msg "comparison failed: 1 < nil" :env "dev" :service "comparer"}
{:level :info :msg "received new processing request: what.png" :env "prod" :service "worker 3"}
{:level :warn :msg "bad configuration value :timeout, using 3h" :env "staging" :service "worker 3"}
`

	rdr := strings.NewReader(input)
	dec := edn.NewDecoder(rdr)
	var err error
	for {
		var entry LogEntry
		err = dec.Decode(&entry)
		if err != nil {
			break
		}
		if entry.Level == edn.Keyword("warn") && entry.Environment != "dev" {
			fmt.Println(entry.Message)
		}
	}
	if err != nil && err != io.EOF {
		// Something bad happened to our reader
		fmt.Println(err)
		return
	}
	// If err == io.EOF then we've reached end of stream
	fmt.Println("End of stream!")
}
Output:

slow response time from 127.0.1.39
worker 8 has been unavailable for 30s
bad configuration value :timeout, using 3h
End of stream!

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFunc         = errors.New("Value is not a function")
	ErrMismatchArities = errors.New("Function does not have single argument in, two argument out")
	ErrNotConcrete     = errors.New("Value is not a concrete non-function type")
	ErrTagOverwritten  = errors.New("Previous tag implementation was overwritten")
)
View Source
var GlobalMathContext = MathContext{
	Mode:      big.ToNearestEven,
	Precision: 192,
}

The GlobalMathContext is the global MathContext. It is used if no other context is provided. See MathContext for example usage.

Functions

func AddTagFn

func AddTagFn(tagname string, fn interface{}) error

AddTagFn adds fn as a converter function for tagname tags to the global TagMap. fn must have the signature func(T) (U, error), where T is the expected input type and U is the output type. See Decoder.AddTagFn for examples.

func AddTagStruct

func AddTagStruct(tagname string, val interface{}) error

AddTagStructs adds the struct as a matching struct for tagname tags to the global TagMap. val can not be a channel, function, interface or an unsafe pointer. See Decoder.AddTagStruct for examples.

func Compact

func Compact(dst *bytes.Buffer, src []byte) error

Compact appends to dst a compacted form of the EDN-encoded src. It does not remove discard values.

func Indent

func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error

Indent writes to dst an indented form of the EDN-encoded src. Each EDN collection begins on a new, indented line beginning with prefix followed by one or more copies of indent according to the indentation nesting. The data written to dst does not begin with the prefix nor any indentation, and has no trailing newline, to make it easier to embed inside other formatted EDN data.

Indent filters away whitespace, including comments and discards.

func IndentStream

func IndentStream(dst io.Writer, src io.Reader, prefix, indent string) error

IndentStream is an implementation of PPrint for generic readers and writers

func Marshal

func Marshal(v interface{}) ([]byte, error)

Marshal returns the EDN encoding of v.

Marshal traverses the value v recursively. If an encountered value implements the Marshaler interface and is not a nil pointer, Marshal calls its MarshalEDN method to produce EDN. The nil pointer exception is not strictly necessary but mimics a similar, necessary exception in the behavior of UnmarshalEDN.

Otherwise, Marshal uses the following type-dependent default encodings:

Boolean values encode as EDN booleans.

Integers encode as EDN integers.

Floating point values encode as EDN floats.

String values encode as EDN strings coerced to valid UTF-8, replacing invalid bytes with the Unicode replacement rune. The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e" to keep some browsers from misinterpreting EDN output as HTML. Ampersand "&" is also escaped to "\u0026" for the same reason.

Array and slice values encode as EDN arrays, except that []byte encodes as a base64-encoded string, and a nil slice encodes as the nil EDN value.

Struct values encode as EDN maps. Each exported struct field becomes a member of the map unless

  • the field's tag is "-", or
  • the field is empty and its tag specifies the "omitempty" option.

The empty values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero. The map's default key is the struct field name as a keyword, but can be specified in the struct field's tag value. The "edn" key in the struct field's tag value is the key name, followed by an optional comma and options. Examples:

// Field is ignored by this package.
Field int `edn:"-"`

// Field appears in EDN as key :my-name.
Field int `edn:"myName"`

// Field appears in EDN as key :my-name and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int `edn:"my-name,omitempty"`

// Field appears in EDN as key :field (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int `edn:",omitempty"`

The "str", "key" and "sym" options signals that a field name should be written as a string, keyword or symbol, respectively. If none are specified, then the default behaviour is to emit them as keywords. Examples:

// Default behaviour: field name will be encoded as :foo
Foo int

// Encode Foo as string with name "string-foo"
Foo int `edn:"string-foo,str"`

// Encode Foo as symbol with name sym-foo
Foo int `edn:"sym-foo,sym"`

Anonymous struct fields are usually marshaled as if their inner exported fields were fields in the outer struct, subject to the usual Go visibility rules amended as described in the next paragraph. An anonymous struct field with a name given in its EDN tag is treated as having that name, rather than being anonymous. An anonymous struct field of interface type is treated the same as having that type as its name, rather than being anonymous.

The Go visibility rules for struct fields are amended for EDN when deciding which field to marshal or unmarshal. If there are multiple fields at the same level, and that level is the least nested (and would therefore be the nesting level selected by the usual Go rules), the following extra rules apply:

1) Of those fields, if any are EDN-tagged, only tagged fields are considered, even if there are multiple untagged fields that would otherwise conflict. 2) If there is exactly one field (tagged or not according to the first rule), that is selected. 3) Otherwise there are multiple fields, and all are ignored; no error occurs.

To force ignoring of an anonymous struct field in both current and earlier versions, give the field a EDN tag of "-".

Map values usually encode as EDN maps. There are no limitations on the keys or values -- as long as they can be encoded to EDN, anything goes. Map values will be encoded as sets if their value type is either a bool or a struct with no fields.

If you want to ensure that a value is encoded as a map, you can specify that as follows:

// Encode Foo as a map, instead of the default set
Foo map[int]bool `edn:",map"`

Arrays and slices are encoded as vectors by default. As with maps and sets, you can specify that a field should be encoded as a list instead, by using the option "list":

// Encode Foo as a list, instead of the default vector
Foo []int `edn:",list"`

Pointer values encode as the value pointed to. A nil pointer encodes as the nil EDN object.

Interface values encode as the value contained in the interface. A nil interface value encodes as the nil EDN value.

Channel, complex, and function values cannot be encoded in EDN. Attempting to encode such a value causes Marshal to return an UnsupportedTypeError.

EDN cannot represent cyclic data structures and Marshal does not handle them. Passing cyclic structures to Marshal will result in an infinite recursion.

Example (Set)
package main

import (
	"fmt"

	"olympos.io/encoding/edn"
)

func main() {
	// values of type map[T]bool and map[T]struct{} are encoded as EDN sets by
	// default
	val := map[int]bool{42: true}

	bs, _ := edn.Marshal(val)
	fmt.Println(string(bs)) // => #{42}

	val2 := map[string]struct{}{"hiccup": {}}
	bs, _ = edn.Marshal(val2)
	fmt.Println(string(bs)) // => #{"hiccup"}

}
Output:

#{42}
#{"hiccup"}
Example (SetOverride)
package main

import (
	"fmt"

	"olympos.io/encoding/edn"
)

func main() {
	// You can specify that map[T]bool/struct{} are printed as EDN maps by using
	// the `map` keyword in the EDN struct tag:
	type Value struct {
		BoolMap   map[int]bool        `edn:"bool-map,map,omitempty"`
		StructMap map[string]struct{} `edn:"struct-map,map,omitempty"`
	}

	var val Value
	val.BoolMap = map[int]bool{2: false}
	bs, _ := edn.Marshal(val)
	fmt.Println(string(bs)) // => {:bool-map{2 false}}

	val.BoolMap = nil
	val.StructMap = map[string]struct{}{"foo": {}}
	bs, _ = edn.Marshal(val)
	fmt.Println(string(bs)) // =>  {:struct-map{"foo"{}}}

}
Output:

{:bool-map{2 false}}
{:struct-map{"foo"{}}}

func MarshalIndent

func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

MarshalIndent is like Marshal but applies Indent to format the output.

func MarshalPPrint

func MarshalPPrint(v interface{}, opts *PPrintOpts) ([]byte, error)

MarshalPPrint is like Marshal but applies PPrint to format the output.

func MustAddTagFn

func MustAddTagFn(tagname string, fn interface{})

MustAddTagFn adds fn as a converter function for tagname tags to the global TagMap like AddTagFn, except this function panics if the tag could not be added.

func PPrint

func PPrint(dst *bytes.Buffer, src []byte, opt *PPrintOpts) error

PPrint writes to dst an indented form of the EDN-encoded src. This implementation attempts to write idiomatic/readable EDN values, in a fashion close to (but not quite equal to) clojure.pprint/pprint.

PPrint filters away whitespace, including comments and discards.

func PPrintStream

func PPrintStream(dst io.Writer, src io.Reader, opt *PPrintOpts) error

PPrintStream is an implementation of PPrint for generic readers and writers

func Unmarshal

func Unmarshal(data []byte, v interface{}) error

Unmarshal parses the EDN-encoded data and stores the result in the value pointed to by v.

Unmarshal uses the inverse of the encodings that Marshal uses, allocating maps, slices, and pointers as necessary, with the following additional rules:

First, if the value to store the result into implements edn.Unmarshaler, it is called.

If the value is tagged and the tag is known, the EDN value is translated into the input of the tag convert function. If no error happens during converting, the result of the conversion is then coerced into v if possible.

To unmarshal EDN into a pointer, Unmarshal first handles the case of the EDN being the EDN literal nil. In that case, Unmarshal sets the pointer to nil. Otherwise, Unmarshal unmarshals the EDN into the value pointed at by the pointer. If the pointer is nil, Unmarshal allocates a new value for it to point to.

To unmarshal EDN into a struct, Unmarshal matches incoming object keys to the keys used by Marshal (either the struct field name or its tag), preferring an exact match but also accepting a case-insensitive match.

To unmarshal EDN into an interface value, Unmarshal stores one of these in the interface value:

bool, for EDN booleans
float64, for EDN floats
int64, for EDN integers
int32, for EDN characters
string, for EDN strings
[]interface{}, for EDN vectors and lists
map[interface{}]interface{}, for EDN maps
map[interface{}]bool, for EDN sets
nil for EDN nil
edn.Tag for unknown EDN tagged elements
T for known EDN tagged elements, where T is the result of the converter function

To unmarshal an EDN vector/list into a slice, Unmarshal resets the slice to nil and then appends each element to the slice.

To unmarshal an EDN map into a Go map, Unmarshal replaces the map with an empty map and then adds key-value pairs from the object to the map.

If a EDN value is not appropriate for a given target type, or if a EDN number overflows the target type, Unmarshal skips that field and completes the unmarshalling as best it can. If no more serious errors are encountered, Unmarshal returns an UnmarshalTypeError describing the earliest such error.

The EDN nil value unmarshals into an interface, map, pointer, or slice by setting that Go value to nil.

When unmarshaling strings, invalid UTF-8 or invalid UTF-16 surrogate pairs are not treated as an error. Instead, they are replaced by the Unicode replacement character U+FFFD.

Example (Set)
package main

import (
	"fmt"

	"olympos.io/encoding/edn"
)

func main() {
	// map[T]bool is considered as sets as well as maps
	var val map[int]bool

	edn.UnmarshalString("#{1 -5 42}", &val)
	fmt.Println(val[42], val[123]) // => true false

	edn.UnmarshalString("{1 false 2 true}", &val)
	fmt.Println(val[1], val[2]) // => false true

}
Output:

true false
false true

func UnmarshalString

func UnmarshalString(data string, v interface{}) error

UnmarshalString works like Unmarshal, but accepts a string as input instead of a byte slice.

func UseJSONAsFallback

func UseJSONAsFallback(val bool)

UseJSONAsFallback can be set to true to let go-edn parse structs with information from the `json` tag for encoding and decoding type fields if not the `edn` tag field is set. This is not threadsafe: Encoding and decoding happening while this is called may return results that mix json and non-json tag reading. Preferably you call this in an init() function to ensure it is either set or unset.

Types

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

A Decoder reads and decodes EDN objects from an input stream.

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder returns a new decoder that reads from r.

The decoder introduces its own buffering and may read data from r beyond the EDN values requested.

func (*Decoder) AddTagFn

func (d *Decoder) AddTagFn(tagname string, fn interface{}) error

AddTagFn adds a tag function to the decoder's TagMap. Note that TagMaps are mutable: If Decoder A and B share TagMap, then adding a tag function to one may modify both.

Example (Complex)
package main

import (
	"fmt"
	"strings"

	"olympos.io/encoding/edn"
)

func main() {
	input := `#complex [14.5 15.5]`

	rdr := strings.NewReader(input)
	dec := edn.NewDecoder(rdr)

	intoComplex := func(v [2]float64) (complex128, error) {
		return complex(v[0], v[1]), nil
	}
	err := dec.AddTagFn("complex", intoComplex)
	if err != nil {
		panic(err)
	}

	var cmplx complex128
	err = dec.Decode(&cmplx)
	if err != nil {
		panic(err)
	}
	fmt.Println(cmplx)
}
Output:

(14.5+15.5i)
Example (Duration)
package main

import (
	"fmt"
	"strings"
	"time"

	"olympos.io/encoding/edn"
)

func main() {
	input := `#com.myapp/duration "2h30m"`

	rdr := strings.NewReader(input)
	dec := edn.NewDecoder(rdr)
	dec.AddTagFn("com.myapp/duration", time.ParseDuration)

	var d time.Duration
	dec.Decode(&d)
	fmt.Println(d)

	input = `#com.myapp/duration "1moment"`
	rdr = strings.NewReader(input)
	dec = edn.NewDecoder(rdr)
	dec.AddTagFn("com.myapp/duration", time.ParseDuration)
	err := dec.Decode(&d)
	if err != nil {
		fmt.Println(err)
	}

}
Output:

2h30m0s
time: unknown unit moment in duration 1moment

func (*Decoder) AddTagStruct

func (d *Decoder) AddTagStruct(tagname string, example interface{}) error

AddTagStruct adds a tag struct to the decoder's TagMap. Note that TagMaps are mutable: If Decoder A and B share TagMap, then adding a tag struct to one may modify both.

Example (IntoInterface)
package main

import (
	"fmt"
	"strings"

	"olympos.io/encoding/edn"
)

type Length interface {
	ToMetres() float64
}

type Foot float64

func (f Foot) ToMetres() float64 {
	return float64(f) * 0.3048
}

type Yard float64

func (y Yard) ToMetres() float64 {
	return float64(y) * 0.9144
}

type Metre float64

func (m Metre) ToMetres() float64 {
	return float64(m)
}

func main() {
	// We can insert things into interfaces with tagged literals.
	// Let's assume we have
	// type Length interface { ToMetres() float64 }
	// and Foot, Yard and Metre which satisfy the Length interface

	input := `[#foot 14.5, #yard 2, #metre 3.0]`
	rdr := strings.NewReader(input)
	dec := edn.NewDecoder(rdr)
	dec.AddTagStruct("foot", Foot(0))
	dec.AddTagStruct("yard", Yard(0))
	dec.AddTagStruct("metre", Metre(0))

	var lengths []Length
	dec.Decode(&lengths)
	for _, len := range lengths {
		fmt.Printf("%.2f\n", len.ToMetres())
	}
}
Output:

4.42
1.83
3.00
Example (Nested)
package main

import (
	"fmt"
	"strings"

	"olympos.io/encoding/edn"
)

func main() {
	// Tag structs and tag functions can nest arbitrarily.
	type Node struct {
		Left  *Node
		Val   int
		Right *Node
	}

	// function for finding the total sum of a tree
	var sumTree func(n Node) int
	sumTree = func(root Node) (val int) {
		if root.Left != nil {
			val += sumTree(*root.Left)
		}
		val += root.Val
		if root.Right != nil {
			val += sumTree(*root.Right)
		}
		return
	}

	input := `#node {:left #node {:val 1}
                   :val 2
                   :right #node {:left #node {:val 5}
                                 :val 8
                                 :right #node {:val 12}}}`
	rdr := strings.NewReader(input)
	dec := edn.NewDecoder(rdr)
	dec.AddTagStruct("node", Node{})
	var node Node
	dec.Decode(&node)
	fmt.Println(sumTree(node))
}
Output:

28

func (*Decoder) Buffered

func (d *Decoder) Buffered() *bufio.Reader

Buffered returns a reader of the data remaining in the Decoder's buffer. The reader is valid until the next call to Decode.

func (*Decoder) Decode

func (d *Decoder) Decode(val interface{}) (err error)

Decode reads the next EDN-encoded value from its input and stores it in the value pointed to by v.

See the documentation for Unmarshal for details about the conversion of EDN into a Go value.

func (*Decoder) DisallowUnknownFields

func (d *Decoder) DisallowUnknownFields()

DisallowUnknownFields causes the Decoder to return an error when the destination is a struct and the input contains keys which do not match any non-ignored, exported fields in the destination.

func (*Decoder) MustAddTagFn

func (d *Decoder) MustAddTagFn(tagname string, fn interface{})

MustAddTagFn adds a tag function to the decoder's TagMap like AddTagFn, except this function also panics if the tag could not be added.

func (*Decoder) UseMathContext

func (d *Decoder) UseMathContext(mc MathContext)

UseMathContext sets the given math context as default math context for this decoder.

Example
package main

import (
	"fmt"
	"math/big"
	"strings"

	"olympos.io/encoding/edn"
)

func main() {
	input := "3.14159265358979323846264338327950288419716939937510M"

	rdr := strings.NewReader(input)
	dec := edn.NewDecoder(rdr)

	mathContext := edn.GlobalMathContext
	// use global math context (does nothing)
	dec.UseMathContext(mathContext)

	var val *big.Float
	dec.Decode(&val)
	fmt.Printf("%.50f\n", val)

	// reread with smaller precision and rounding towards zero
	rdr = strings.NewReader(input)
	dec = edn.NewDecoder(rdr)

	mathContext.Precision = 30
	mathContext.Mode = big.ToZero
	dec.UseMathContext(mathContext)

	dec.Decode(&val)
	fmt.Printf("%.50f\n", val)

}
Output:

3.14159265358979323846264338327950288419716939937510
3.14159265160560607910156250000000000000000000000000

func (*Decoder) UseTagMap

func (d *Decoder) UseTagMap(tm *TagMap)

UseTagMap sets the TagMap provided as the TagMap for this decoder.

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

An Encoder writes EDN values to an output stream.

func NewEncoder

func NewEncoder(w io.Writer) *Encoder

NewEncoder returns a new encoder that writes to w.

func (*Encoder) Encode

func (e *Encoder) Encode(v interface{}) error

Encode writes the EDN encoding of v to the stream, followed by a newline character.

See the documentation for Marshal for details about the conversion of Go values to EDN.

func (*Encoder) EncodeIndent

func (e *Encoder) EncodeIndent(v interface{}, prefix, indent string) error

EncodeIndent writes the indented EDN encoding of v to the stream, followed by a newline character.

See the documentation for MarshalIndent for details about the conversion of Go values to EDN.

func (*Encoder) EncodePPrint

func (e *Encoder) EncodePPrint(v interface{}, opts *PPrintOpts) error

EncodePPrint writes the pretty-printed EDN encoding of v to the stream, followed by a newline character.

See the documentation for MarshalPPrint for details about the conversion of Go values to EDN.

type InvalidUnmarshalError

type InvalidUnmarshalError struct {
	Type reflect.Type
}

An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. (The argument to Unmarshal must be a non-nil pointer.)

func (*InvalidUnmarshalError) Error

func (e *InvalidUnmarshalError) Error() string

type Keyword

type Keyword string

A Keyword is an EDN keyword without : prepended in front.

Example
package main

import (
	"fmt"

	"olympos.io/encoding/edn"
)

func main() {
	const Friday = edn.Keyword("friday")
	fmt.Println(Friday)

	input := `:friday`
	var weekday edn.Keyword
	edn.UnmarshalString(input, &weekday)

	if weekday == Friday {
		fmt.Println("It is friday!")
	}
}
Output:

:friday
It is friday!

func (Keyword) MarshalEDN

func (k Keyword) MarshalEDN() ([]byte, error)

func (Keyword) String

func (k Keyword) String() string

type Marshaler

type Marshaler interface {
	MarshalEDN() ([]byte, error)
}

Marshaler is the interface implemented by objects that can marshal themselves into valid EDN.

type MarshalerError

type MarshalerError struct {
	Type reflect.Type
	Err  error
}

A MarshalerError is returned by Marshal when encoding a type with a MarshalEDN function fails.

func (*MarshalerError) Error

func (e *MarshalerError) Error() string

type MathContext

type MathContext struct {
	Precision uint
	Mode      big.RoundingMode
}

A MathContext specifies the precision and rounding mode for `math/big.Float`s when decoding.

Example (Global)
package main

import (
	"fmt"
	"math/big"

	"olympos.io/encoding/edn"
)

func main() {
	input := "1.12345678901234567890123456789012345678901234567890M"
	var val *big.Float
	edn.UnmarshalString(input, &val)
	fmt.Printf("%.50f\n", val)

	// override default precision
	mathContext := edn.GlobalMathContext
	edn.GlobalMathContext.Precision = 30
	edn.UnmarshalString(input, &val)
	fmt.Printf("%.50f\n", val)

	// revert it back to original values
	edn.GlobalMathContext = mathContext

}
Output:

1.12345678901234567890123456789012345678901234567890
1.12345678918063640594482421875000000000000000000000

type PPrintOpts

type PPrintOpts struct {
	RightMargin int
	MiserWidth  int
}

PPrintOpts is a configuration map for PPrint. The values in this struct has no effect as of now.

type RawMessage

type RawMessage []byte

RawMessage is a raw encoded, but valid, EDN value. It implements Marshaler and Unmarshaler and can be used to delay EDN decoding or precompute an EDN encoding.

func (RawMessage) MarshalEDN

func (m RawMessage) MarshalEDN() ([]byte, error)

MarshalEDN returns m as the EDN encoding of m.

func (*RawMessage) UnmarshalEDN

func (m *RawMessage) UnmarshalEDN(data []byte) error

UnmarshalEDN sets *m to a copy of data.

type Rune

type Rune rune

A Rune type is a wrapper for a rune. It can be used to encode runes as characters instead of int32 values.

Example
package main

import (
	"fmt"

	"olympos.io/encoding/edn"
)

func main() {
	runeSlice := []edn.Rune{'a', 'b', 'c', ',', ' ', '\n'}

	bs, _ := edn.Marshal(runeSlice)

	fmt.Println(string(bs))
}
Output:

[\a \b \c \u002c \space \newline]

func (Rune) MarshalEDN

func (r Rune) MarshalEDN() ([]byte, error)

type Symbol

type Symbol string

A Symbol is an EDN symbol.

func (Symbol) MarshalEDN

func (s Symbol) MarshalEDN() ([]byte, error)

func (Symbol) String

func (s Symbol) String() string

type SyntaxError

type SyntaxError struct {
	Offset int64 // error occurred after reading Offset bytes
	// contains filtered or unexported fields
}

A SyntaxError is a description of an EDN syntax error.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

type Tag

type Tag struct {
	Tagname string
	Value   interface{}
}

A Tag is a tagged value. The Tagname represents the name of the tag, and the Value is the value of the element.

Example (Reading)
package main

import (
	"fmt"

	"olympos.io/encoding/edn"
)

func main() {
	input := "#unknown ???"

	var tag edn.Tag
	edn.UnmarshalString(input, &tag)

	fmt.Printf("Tag with name %s and value %q of type %T\n", tag.Tagname, tag.Value, tag.Value)
}
Output:

Tag with name unknown and value "???" of type edn.Symbol

func (Tag) MarshalEDN

func (t Tag) MarshalEDN() ([]byte, error)

func (Tag) String

func (t Tag) String() string

func (*Tag) UnmarshalEDN

func (t *Tag) UnmarshalEDN(bs []byte) error

type TagMap

type TagMap struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

A TagMap contains mappings from tag literals to functions and structs that is used when decoding.

func (*TagMap) AddTagFn

func (tm *TagMap) AddTagFn(tagname string, fn interface{}) error

AddTagFn adds fn as a converter function for tagname tags to this TagMap. fn must have the signature func(T) (U, error), where T is the expected input type and U is the output type. See Decoder.AddTagFn for examples.

func (*TagMap) AddTagStruct

func (tm *TagMap) AddTagStruct(tagname string, val interface{}) error

AddTagStructs adds the struct as a matching struct for tagname tags to this TagMap. val can not be a channel, function, interface or an unsafe pointer. See Decoder.AddTagStruct for examples.

func (*TagMap) MustAddTagFn

func (tm *TagMap) MustAddTagFn(tagname string, fn interface{})

MustAddTagFn adds fn as a converter function for tagname tags to this TagMap like AddTagFn, except this function panics if the tag could not be added.

type UnhashableError

type UnhashableError struct {
	Position int64
}

UnhashableError is an error which occurs when the decoder attempted to assign an unhashable key to a map or set. The position close to where value was found is provided to help debugging.

func (*UnhashableError) Error

func (e *UnhashableError) Error() string

type UnknownFieldError

type UnknownFieldError struct {
	Field string       // the field name
	Type  reflect.Type // type of Go struct with a missing field
}

func (*UnknownFieldError) Error

func (e *UnknownFieldError) Error() string

type UnknownTagError

type UnknownTagError struct {
	// contains filtered or unexported fields
}

func (UnknownTagError) Error

func (ute UnknownTagError) Error() string

type UnmarshalTypeError

type UnmarshalTypeError struct {
	Value string       // description of EDN value - "bool", "array", "number -5"
	Type  reflect.Type // type of Go value it could not be assigned to
}

An UnmarshalTypeError describes a EDN value that was not appropriate for a value of a specific Go type.

func (*UnmarshalTypeError) Error

func (e *UnmarshalTypeError) Error() string

type Unmarshaler

type Unmarshaler interface {
	UnmarshalEDN([]byte) error
}

Unmarshaler is the interface implemented by objects that can unmarshal an EDN description of themselves. The input can be assumed to be a valid encoding of an EDN value. UnmarshalEDN must copy the EDN data if it wishes to retain the data after returning.

type UnsupportedTypeError

type UnsupportedTypeError struct {
	Type reflect.Type
}

An UnsupportedTypeError is returned by Marshal when attempting to encode an unsupported value type.

func (*UnsupportedTypeError) Error

func (e *UnsupportedTypeError) Error() string

type UnsupportedValueError

type UnsupportedValueError struct {
	Value reflect.Value
	Str   string
}

An UnsupportedValueError is returned by Marshal when attempting to encode an unsupported value. Examples include the float values NaN and Infinity.

func (*UnsupportedValueError) Error

func (e *UnsupportedValueError) Error() string

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL