textcoder

package
v0.0.16 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2021 License: Apache-2.0, BSD-3-Clause Imports: 5 Imported by: 0

Documentation

Overview

Package textcoder defines a registry of Go types and associated textual encoding/decoding functions.

Unlike the standard library's encoding package, textcoder allows any package to define a coder for a type instead of requiring methods to be defined on the type itself.

For each type T registered in the default registry (using Register or MustRegister), textcoder.DefaultRegistry().GetCoder(reflect.TypeOf(t)) will return a Coder object for marshaling and unmarshaling a textual value. The Coder object returned has an EncodeText() method that takes *Context and a second argument of type T. The DecodeText method of the coder takes a *Context, a string, and an interace{} type that should be a non-nil pointer of type *T.

If a type T implements the encoding.TextUnmarshaler interface, a Registry.GetDecoder(T) will return a Decoder that dispatches to UnmarshalText; the same is true of encoding.TextMarshaler and GetEncoder.

If a type T has an underlying type that is a basic type (bool, int, string, uint, uint8, float32, etc.), textcoder.Registry.GetCoder, GetEncoder, and GetDecoder will return a coder for T based on the underlying type. This allows types like `type distance float64` to use float64's encoder. Due to limitations of Go's reflect package, which does not support obtaining the underlying type of a named type, this functionality is limited to types with an underlying basic type (see https://github.com/golang/go/issues/39574).

Currently the registry does not attempt to find registered coders based on registered interface types. While coders may be registered for an interface type, that coder only be returned then the reflect.Type of the interface is used to obtain a Coder, Encoder, or Decoder; the coder will not be returned if a coder is requested of a type that implements the registered interface. This behavior is likely to change in the future.

The types string, int, uint, float64, float32, uint8, int8, uint16, int16, uint32, int32, uint64, and int64 have coders registered in the default registry. This means these basic types can be encoded and decoded from strings. Most of these types use the functions in strconv to parse and fmt.Sprintf("%d" or "%f") to format. The bool coder is case insensitive and accepts values like "true", "YeS", "on", and "1". These coders may be added to other registries using RegisterBasicTypes().

See the examples for usage.

Example (A)
type x []string

MustRegister(
	reflect.TypeOf(x{}),
	func(v x) (string, error) {
		return strings.Join([]string(v), " | "), nil
	},
	func(s string, dst *x) error {
		*dst = x(strings.Split(s, " | "))
		return nil
	})

v := x{}
err := Unmarshal("a | b | c", &v)
fmt.Printf("%+v, err = %v\n", v, err != nil)

s, err := Marshal(x{"hello", "world"})
fmt.Printf("%s, err = %v\n", s, err != nil)
Output:

[a b c], err = false
hello | world, err = false
Example (B)
r := NewRegistry()

type i3 struct{ x, y, z int64 }

r.Register(
	reflect.TypeOf(i3{}),
	func(v i3) (string, error) {
		return fmt.Sprintf("(%d, %d, %d)", v.x, v.y, v.z), nil
	},
	func(s string, dst *i3) error {
		ints, err := splitAndParseInts(s)
		if err != nil {
			return err
		}
		if len(ints) != 3 {
			return fmt.Errorf("got %d values, want 3", len(ints))
		}
		dst.x, dst.y, dst.z = ints[0], ints[1], ints[2]
		return nil
	})

for _, input := range []string{"1,2,3", "1,2,5,6"} {
	vec := i3{}
	decoder := r.GetDecoder(reflect.TypeOf(vec))
	if err := decoder.DecodeText(nil, input, &vec); err != nil {
		fmt.Printf("error: %v\n", err)
		return
	}
	fmt.Printf("(%d, %d, %d)\n", vec.x, vec.y, vec.z)
}
Output:

(1, 2, 3)
error: got 4 values, want 3
Example (C)
package main

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

type r2 struct{ x, y float64 }

func (v *r2) UnmarshalText(text []byte) error {
	s := string(text)
	parts := strings.Split(s, ",")
	if len(parts) != 2 {
		return fmt.Errorf("bad input to r2 decoder: %q", s)
	}
	x, err := strconv.ParseFloat(parts[0], 64)
	if err != nil {
		return err
	}
	y, err := strconv.ParseFloat(parts[1], 64)
	if err != nil {
		return err
	}
	v.x, v.y = x, y
	return nil
}

func main() {
	r := NewRegistry()

	vec := r2{}
	decoder := r.GetDecoder(reflect.TypeOf(vec))
	if err := decoder.DecodeText(nil, "1.3,2.4", &vec); err != nil {
		fmt.Printf("error: %v", err)
		return
	}

	fmt.Printf("(%.1f, %.1f)\n", vec.x, vec.y)
}
Output:

(1.3, 2.4)
Example (CoderUsingByUnderlyingBasicType)
// Since x has an underlying basic type, its coder will be be used to
// implement a Coder for x if x has no explicitly registered coder.
type x int64

v := x(3)
err := Unmarshal("46", &v)
fmt.Printf("%d, err = %v\n", v, err != nil)

s, err := Marshal(x(77))
fmt.Printf("%q, err = %v\n", s, err != nil)
Output:

46, err = false
"77", err = false

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Marshal

func Marshal(value T) (string, error)

Marshal attempts to encode the value into a string using one of the default registered coders.

To use a registered coder of type T, value should be of type T.

func MarshalContext

func MarshalContext(ctx *Context, value T) (string, error)

MarshalContext attempts to encode the value into a string using one of the default registered coders.

To use a registered coder of type T, value should be of type T.

func MustRegister

func MustRegister(t reflect.Type, encoder, decoder interface{})

MustRegister registers an encoding function and a decoding function for parsing and printing values of the provided type. This function panics if registration fails; use Register if a panic is unacceptable.

To use the default registry, most users should make calls to MustRegister in an init() function.

See Registry.Register for details of the signatures of encoder and decoder.

func Register

func Register(t reflect.Type, encoder, decoder interface{}) error

Register registers an encoding function and a decoding function for parsing and printing values of the provided type.

See Registry.Register for details of the signatures of encoder and decoder.

func RegisterBasicTypes

func RegisterBasicTypes(r *Registry) error

RegisterBasicTypes attempts to register coders for the following types: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, string, float32, float64.

func Unmarshal

func Unmarshal(value string, dst T) error

Unmarshal decodes a textual value into dst.

func UnmarshalContext

func UnmarshalContext(ctx *Context, value string, dst T) error

UnmarshalContext is like Unmarshal, but it takes an extra context argument.

To use a registered coder of type T, dst should be of type *T.

Types

type Coder

type Coder interface {
	Encoder
	Decoder
}

Coder implements both Encoder and Decoder for the same type.

type Context

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

Context is passed to the encoder or decoder as a weigh of passing arbitrary contextual formatting information.

Example
package main

import (
	"fmt"
	"reflect"
	"strings"
)

type bulletList struct {
	bullet string
	items  []interface{}
}

type noEncoderType struct{}

func main() {

	MustRegister(
		reflect.TypeOf(""),
		func(v string) (string, error) { return v, nil },
		func(text string, dst *string) error { *dst = text; return nil })

	MustRegister(
		reflect.TypeOf(&bulletList{}),
		func(ctx *Context, v *bulletList) (string, error) {
			indent := ""
			if v, ok := ctx.Value("indent"); ok {
				indent = v.(string)
			}
			var bullets []string
			indentCtx := ctx.WithValue("indent", indent+"  ")
			for _, item := range v.items {
				itemEncoder := ctx.Registry().GetEncoder(reflect.TypeOf(item))
				var itemText string
				if itemEncoder == nil {
					itemText = fmt.Sprintf("missing encoder for %s", reflect.TypeOf(item))
				} else {
					txt, err := itemEncoder.EncodeText(indentCtx, item)
					if err != nil {
						return "", fmt.Errorf("error encoding list: %w", err)
					}
					itemText = txt
				}

				prefix := fmt.Sprintf("%s%s ", indent, v.bullet)
				if _, ok := item.(*bulletList); ok {
					prefix = indent
				}
				bullets = append(bullets, prefix+itemText)
			}
			return strings.Join(bullets, "\n"), nil
		},
		func(s string, dst **bulletList) error {
			return fmt.Errorf("decoding not supported")
		})

	v := &bulletList{
		bullet: "-",
		items: []interface{}{
			"a",
			"b",
			noEncoderType{},
			&bulletList{
				bullet: "*",
				items: []interface{}{
					"c",
					"d",
				},
			},
		},
	}
	s, _ := Marshal(v)
	fmt.Printf("%s\n", s)

}
Output:

- a
- b
- missing encoder for textcoder.noEncoderType
  * c
  * d

func NewContext

func NewContext() *Context

NewContext returns a new Context to use when parsing within a given Row.

func (*Context) Registry

func (c *Context) Registry() *Registry

Registry returns the registry to use for encoding and decoding textual values.

func (*Context) Value

func (c *Context) Value(key interface{}) (interface{}, bool)

Value returns a value associated with the given key.

This may be used to pass contextual information to the text coder that may be relevant for printing out the

func (*Context) WithValue

func (c *Context) WithValue(key, value interface{}) *Context

WithValue returns a new context with an additional key and value.

WithValue does not modify the receiver.

type Decoder

type Decoder interface {
	// DecodeText decodes the given text into the destination dst. dst must not
	// be nil.
	DecodeText(ctx *Context, text string, dst T) error
}

Decoder decodes a textual representation of a value into dstValuePointer, which is of type T*.

type Encoder

type Encoder interface {
	// EncodeText returns the textual form of the value. The value should be of
	// type T where T is a registered type.
	EncodeText(ctx *Context, value T) (string, error)
}

Encoder encodes an argument of type T into a string.

type Registry

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

Registry is a set of registered text coders.

Typically users will use the default registry, but creating a specialized Registry object is fully supported.

func DefaultRegistry

func DefaultRegistry() *Registry

DefaultRegistry returns the default registry for registring text coders.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns a new object for registring text coders.

func (*Registry) GetCoder

func (r *Registry) GetCoder(t reflect.Type) Coder

GetCoder returns the Coder for the given type or nil if no coder can be determined. The coder can be used to marshal and unmarshal textual representations of values of the given type.

The coder will be determined based on applying the following rules in order. Let T be the argument to GetCoder:

1. If the type is explicitly registered because of a previous call to r.Register(t), r.GetCoder(t) will return that coder.

2. If the type implements encoding.TextUnmarshaler and encoding.TextMarshaler interface, GetCoder(t) returns a Decoder that dispatches to those methods.

3. If the type has an underlying type that is a basic type (bool, int, string, uint, uint8, float32, etc.), GetCoder(t) will return a coder for T based on the underlying type. This allows types like `type distance float64` to use float64's Coder. Due to limitations of Go's reflect package, which does not support obtaining the underlying type of a named type, this functionality is limited to types with an underlying basic type (see https://github.com/golang/go/issues/39574).

Currently the registry does not attempt to find registered coders based on registered interface types. While coders may be registered for an interface type, that coder only be returned then the reflect.Type of the interface is used to obtain a Coder, Encoder, or Decoder; the coder will not be returned if a coder is requested of a type that implements the registered interface. This behavior is likely to change in the future.

The types string, int, uint, float64, float32, uint8, int8, uint16, int16, uint32, int32, uint64, and int64 have coders registered in the default registry. This means these basic types can be encoded and decoded from strings. Most of these types use the functions in strconv to parse and fmt.Sprintf("%d" or "%f") to format. The bool coder is case insensitive and accepts values like "true", "YeS", "on", and "1". These coders may be added to other registries using RegisterBasicTypes().

func (*Registry) GetDecoder

func (r *Registry) GetDecoder(t reflect.Type) Decoder

GetDecoder returns the decoder for the given type or nil.

The decoder will be determined based on applying the following rules in order:

1. If the type is explicitly registered because of a previous call to r.Register(t), r.GetDecoder(t) will return that decoder.

2. If the type implements encoding.TextUnmarshaler interface, GetDecoder(t) returns an decoder that dispatches to UnmarshalText.

3. If the type has an underlying type that is a basic type (bool, int, string, uint, uint8, float32, etc.), GetDecoder(t) will return a decoder for t based on the underlying type.

Example
r := NewRegistry()

r.Register(
	reflect.TypeOf(int(0)),
	func(v int) (string, error) {
		return strconv.Itoa(v), nil
	},
	func(s string, dst *int) error {
		i, err := strconv.Atoi(s)
		*dst = i
		return err
	})

dec := r.GetDecoder(reflect.TypeOf(int(42)))

i1 := 0
dec.DecodeText(NewContext(), "43", &i1)
fmt.Printf("%d\n", i1)

err := dec.DecodeText(NewContext(), "nan", &i1)
fmt.Printf("error: %v\n", err != nil)
Output:

43
error: true

func (*Registry) GetEncoder

func (r *Registry) GetEncoder(t reflect.Type) Encoder

GetEncoder returns the encoder for the given type or nil.

The encoder will be determined based on applying the following rules in order:

1. If the type is explicitly registered because of a previous call to r.Register(t), r.GetEncoder(t) will return that encoder.

2. If the type implements encoding.TextMarshaler interface, GetEncoder(t) returns an encoder that dispatches to MarshalText.

3. If the type has an underlying type that is a basic type (bool, int, string, uint, uint8, float32, etc.), GetEncoder(t) will return a encoder for t based on the underlying type.

Example
r := NewRegistry()

r.Register(
	reflect.TypeOf(int(0)),
	func(v int) (string, error) {
		return strconv.Itoa(v), nil
	},
	func(s string, dst *int) error {
		i, err := strconv.Atoi(s)
		*dst = i
		return err
	})

enc := r.GetEncoder(reflect.TypeOf(int(42)))

s, err := enc.EncodeText(NewContext(), int(54))
fmt.Printf("%q, %v\n", s, err != nil)
Output:

"54", false

func (*Registry) NewContext

func (r *Registry) NewContext() *Context

NewContext returns a new context that uses this registry for textual encoding purposes.

func (*Registry) Register

func (r *Registry) Register(t reflect.Type, encoder, decoder interface{}) error

Register registers an encoding function and a decoding function for parsing and printing values of the provided type.

If the provided type is T, the decoder should have one of the following signatures:

1. func(*textcoder.Context, *T) error

2. func(string, *T) error

The encoder should have one of the following signatures:

1. func(*textcoder.Context, T) (string, error).

2. func(T) (string, error).

Encoders and decoders should take a Context argument if they need to make nested calls to textcoder functions.

type T

type T = interface{}

T is used in place of interface{} to represent a templated type. When go adds generics, we can revisit the API.

Jump to

Keyboard shortcuts

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