pax_global_header00006660000000000000000000000064135332723170014520gustar00rootroot0000000000000052 comment=0275d16782562f77a842ef80583eea89a715f613 tmc-0.5.1/000077500000000000000000000000001353327231700123065ustar00rootroot00000000000000tmc-0.5.1/.github/000077500000000000000000000000001353327231700136465ustar00rootroot00000000000000tmc-0.5.1/.github/workflows/000077500000000000000000000000001353327231700157035ustar00rootroot00000000000000tmc-0.5.1/.github/workflows/test.yml000066400000000000000000000012401353327231700174020ustar00rootroot00000000000000name: Test on: [push, pull_request] jobs: build: name: Test runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: - name: Set up Go uses: actions/setup-go@v1 with: go-version: 1.12 id: go - name: Checkout uses: actions/checkout@v1 - name: Test run: go test . -coverprofile=coverage.txt -covermode=atomic - name: Test race run: go test -race . - name: Upload to Codecov if: matrix.os == 'ubuntu-latest' env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} run: bash <(curl -s https://codecov.io/bash) tmc-0.5.1/.gitignore000066400000000000000000000003151353327231700142750ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test coverage.txt # Output of the go coverage tool, specifically when used with LiteIDE *.out tmc-0.5.1/LICENSE000066400000000000000000000020651353327231700133160ustar00rootroot00000000000000MIT License Copyright (c) 2019 Bjørn Erik Pedersen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tmc-0.5.1/README.md000066400000000000000000000120061353327231700135640ustar00rootroot00000000000000

Codec for a Typed Map

Provides round-trip serialization of typed Go maps.

### How to Use See the [GoDoc](https://godoc.org/github.com/bep/tmc) for some basic examples and how to configure custom codec, adapters etc. ### Why? Text based serialization formats like JSON and YAML are convenient, but when used with Go maps, most type information gets lost in translation. Listed below is a round-trip example in JSON (see https://play.golang.org/p/zxt-wi4Ljz3 for a runnable version): ```go package main import ( "encoding/json" "log" "math/big" "time" "github.com/kr/pretty" ) func main() { mi := map[string]interface{}{ "vstring": "Hello", "vint": 32, "vrat": big.NewRat(1, 2), "vtime": time.Now(), "vduration": 3 * time.Second, "vsliceint": []int{1, 3, 4}, "nested": map[string]interface{}{ "vint": 55, "vduration": 5 * time.Second, }, "nested-typed-int": map[string]int{ "vint": 42, }, "nested-typed-duration": map[string]time.Duration{ "v1": 5 * time.Second, "v2": 10 * time.Second, }, } data, err := json.Marshal(mi) if err != nil { log.Fatal(err) } m := make(map[string]interface{}) if err := json.Unmarshal(data, &m); err != nil { log.Fatal(err) } pretty.Print(m) } ``` This prints: ```go map[string]interface {}{ "vint": float64(32), "vrat": "1/2", "vtime": "2009-11-10T23:00:00Z", "vduration": float64(3e+09), "vsliceint": []interface {}{ float64(1), float64(3), float64(4), }, "vstring": "Hello", "nested": map[string]interface {}{ "vduration": float64(5e+09), "vint": float64(55), }, "nested-typed-duration": map[string]interface {}{ "v2": float64(1e+10), "v1": float64(5e+09), }, "nested-typed-int": map[string]interface {}{ "vint": float64(42), }, } ``` And that is very different from the origin: * All numbers are now `float64` * `time.Duration` is also `float64` * `time.Now` and `*big.Rat` are strings * Slices are `[]interface {}`, maps `map[string]interface {}` So, for structs, you can work around some of the limitations above with custom `MarshalJSON`, `UnmarshalJSON`, `MarshalText` and `UnmarshalText`. For the commonly used flexible and schema-less`map[string]interface {}` this is, as I'm aware of, not an option. Using this library, the above can be written to (see https://play.golang.org/p/PlDetQP5aWd for a runnable example): ```go package main import ( "log" "math/big" "time" "github.com/bep/tmc" "github.com/kr/pretty" ) func main() { mi := map[string]interface{}{ "vstring": "Hello", "vint": 32, "vrat": big.NewRat(1, 2), "vtime": time.Now(), "vduration": 3 * time.Second, "vsliceint": []int{1, 3, 4}, "nested": map[string]interface{}{ "vint": 55, "vduration": 5 * time.Second, }, "nested-typed-int": map[string]int{ "vint": 42, }, "nested-typed-duration": map[string]time.Duration{ "v1": 5 * time.Second, "v2": 10 * time.Second, }, } c, err := tmc.New() if err != nil { log.Fatal(err) } data, err := c.Marshal(mi) if err != nil { log.Fatal(err) } m := make(map[string]interface{}) if err := c.Unmarshal(data, &m); err != nil { log.Fatal(err) } pretty.Print(m) } ``` This prints: ```go map[string]interface {}{ "vduration": time.Duration(3000000000), "vint": int(32), "nested-typed-int": map[string]int{"vint":42}, "vsliceint": []int{1, 3, 4}, "vstring": "Hello", "vtime": time.Time{ wall: 0x0, ext: 63393490800, loc: (*time.Location)(nil), }, "nested": map[string]interface {}{ "vduration": time.Duration(5000000000), "vint": int(55), }, "nested-typed-duration": map[string]time.Duration{"v1":5000000000, "v2":10000000000}, "vrat": &big.Rat{ a: big.Int{ neg: false, abs: {0x1}, }, b: big.Int{ neg: false, abs: {0x2}, }, }, } ``` ### Performance The implementation is easy to reason aobut (it uses reflection), but It's not particulary fast and probably not suited for _big data_. A simple benchmark with a roundtrip marshal/unmarshal is included. On my MacBook it shows: ```bash BenchmarkCodec/JSON_regular-4 50000 27523 ns/op 6742 B/op 171 allocs/op BenchmarkCodec/JSON_typed-4 20000 66644 ns/op 16234 B/op 411 allocs/op ``` tmc-0.5.1/adapters.go000066400000000000000000000066541353327231700144530ustar00rootroot00000000000000// Copyright © 2019 Bjørn Erik Pedersen . // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package tmc import ( "encoding" "fmt" "math/big" "reflect" "strconv" "time" ) // Adapter wraps a type to preserve type information when encoding and decoding // a map. // // The simples way to create new adapters is via the NewAdapter function. type Adapter interface { FromString(s string) (interface{}, error) MarshalText() (text []byte, err error) Type() reflect.Type Wrap(v interface{}) Adapter } var ( // DefaultTypeAdapters contains the default set of type adapters. DefaultTypeAdapters = []Adapter{ // Time NewAdapter(time.Now(), nil, nil), NewAdapter( 3*time.Hour, func(s string) (interface{}, error) { return time.ParseDuration(s) }, func(v interface{}) (string, error) { return v.(time.Duration).String(), nil }, ), // Numbers NewAdapter(big.NewRat(1, 2), nil, nil), NewAdapter( int(32), func(s string) (interface{}, error) { return strconv.Atoi(s) }, func(v interface{}) (string, error) { return strconv.Itoa(v.(int)), nil }, ), } ) // NewAdapter creates a new adapter that wraps the target type. // // fromString can be omitted if target implements encoding.TextUnmarshaler. // toString can be omitted if target implements encoding.TextMarshaler. // // It will panic if it can not be created. func NewAdapter( target interface{}, fromString func(s string) (interface{}, error), toString func(v interface{}) (string, error)) Adapter { targetValue := reflect.ValueOf(target) targetType := targetValue.Type() wasPointer := targetType.Kind() == reflect.Ptr if !wasPointer { // Need the pointer to see the TextUnmarshaler implementation. v := targetValue targetValue = reflect.New(targetType) targetValue.Elem().Set(v) } if fromString == nil { if _, ok := targetValue.Interface().(encoding.TextUnmarshaler); ok { fromString = func(s string) (interface{}, error) { typ := targetType if typ.Kind() == reflect.Ptr { typ = typ.Elem() } v := reflect.New(typ) err := v.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(s)) if err != nil { return nil, err } if !wasPointer { v = v.Elem() } return v.Interface(), nil } } else { panic(fmt.Sprintf("%T can not be unmarshaled", target)) } } var marshalText func(v interface{}) ([]byte, error) if toString != nil { marshalText = func(v interface{}) ([]byte, error) { s, err := toString(v) return []byte(s), err } } else if _, ok := target.(encoding.TextMarshaler); ok { marshalText = func(v interface{}) ([]byte, error) { return v.(encoding.TextMarshaler).MarshalText() } } else { panic(fmt.Sprintf("%T can not be marshaled", target)) } return &adapter{ targetType: targetType, fromString: fromString, marshalText: marshalText, } } var _ Adapter = (*adapter)(nil) type adapter struct { fromString func(s string) (interface{}, error) marshalText func(v interface{}) (text []byte, err error) targetType reflect.Type target interface{} } func (a *adapter) FromString(s string) (interface{}, error) { return a.fromString(s) } func (a *adapter) MarshalText() (text []byte, err error) { return a.marshalText(a.target) } func (a adapter) Type() reflect.Type { return a.targetType } func (a adapter) Wrap(v interface{}) Adapter { a.target = v return &a } tmc-0.5.1/codec.go000066400000000000000000000176501353327231700137230ustar00rootroot00000000000000// Copyright © 2019 Bjørn Erik Pedersen . // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package tmc import ( "encoding/json" "errors" "fmt" "reflect" "strings" ) // JSONMarshaler encodes and decodes JSON and is the default used in this // codec. var JSONMarshaler = new(jsonMarshaler) // New creates a new Coded with some optional options. func New(opts ...Option) (*Codec, error) { c := &Codec{ typeSep: "|", marshaler: JSONMarshaler, typeAdapters: DefaultTypeAdapters, typeAdaptersMap: make(map[reflect.Type]Adapter), typeAdaptersStringMap: make(map[string]Adapter), } for _, opt := range opts { if err := opt(c); err != nil { return c, err } } for _, w := range c.typeAdapters { tp := w.Type() c.typeAdaptersMap[tp] = w c.typeAdaptersStringMap[tp.String()] = w } return c, nil } // Option configures the Codec. type Option func(c *Codec) error // WithTypeSep sets the separator to use before the type information encoded in // the key field. Default is "|". func WithTypeSep(sep string) func(c *Codec) error { return func(c *Codec) error { if sep == "" { return errors.New("separator cannot be empty") } c.typeSep = sep return nil } } // WithMarshalUnmarshaler sets the MarshalUnmarshaler to use. // Default is JSONMarshaler. func WithMarshalUnmarshaler(marshaler MarshalUnmarshaler) func(c *Codec) error { return func(c *Codec) error { c.marshaler = marshaler return nil } } // WithTypeAdapters sets the type adapters to use. Note that if more than one // adapter exists for the same type, the last one will win. This means that // if you want to use the default adapters, but override some of them, you // can do: // // adapters := append(typedmapcodec.DefaultTypeAdapters, mycustomAdapters ...) // codec := typedmapcodec.New(WithTypeAdapters(adapters)) // func WithTypeAdapters(typeAdapters []Adapter) func(c *Codec) error { return func(c *Codec) error { c.typeAdapters = typeAdapters return nil } } // Codec provides methods to marshal and unmarshal a Go map while preserving // type information. type Codec struct { typeSep string marshaler MarshalUnmarshaler typeAdapters []Adapter typeAdaptersMap map[reflect.Type]Adapter typeAdaptersStringMap map[string]Adapter } // Marshal accepts a Go map and marshals it to the configured marshaler // anntated with type information. func (c *Codec) Marshal(v interface{}) ([]byte, error) { m, err := c.toTypedMap(v) if err != nil { return nil, err } return c.marshaler.Marshal(m) } // Unmarshal unmarshals the given data to the given Go map, using // any annotated type information found to preserve the type information // stored in Marshal. func (c *Codec) Unmarshal(data []byte, v interface{}) error { if err := c.marshaler.Unmarshal(data, v); err != nil { return err } _, err := c.fromTypedMap(v) return err } func (c *Codec) newKey(key reflect.Value, a Adapter) reflect.Value { return reflect.ValueOf(fmt.Sprintf("%s%s%s", key, c.typeSep, a.Type())) } func (c *Codec) fromTypedMap(mi interface{}) (reflect.Value, error) { m := reflect.ValueOf(mi) if m.Kind() == reflect.Ptr { m = m.Elem() } if m.Kind() != reflect.Map { return reflect.Value{}, errors.New("must be a Map") } keyKind := m.Type().Key().Kind() if keyKind == reflect.Interface { // We only support string keys. // YAML creates map[interface {}]interface {}, so try to convert it. var err error m, err = c.toStringMap(m) if err != nil { return reflect.Value{}, err } } for _, key := range m.MapKeys() { v := indirectInterface(m.MapIndex(key)) var ( keyStr = key.String() keyPlain string keyType string ) sepIdx := strings.LastIndex(keyStr, c.typeSep) if sepIdx != -1 { keyPlain = keyStr[:sepIdx] keyType = keyStr[sepIdx+len(c.typeSep):] } adapter, found := c.typeAdaptersStringMap[keyType] if !found { if v.Kind() == reflect.Map { var err error v, err = c.fromTypedMap(v.Interface()) if err != nil { return reflect.Value{}, err } m.SetMapIndex(key, v) } continue } switch v.Kind() { case reflect.Map: mm := reflect.MakeMap(reflect.MapOf(stringType, adapter.Type())) for _, key := range v.MapKeys() { vv := indirectInterface(v.MapIndex(key)) nv, err := adapter.FromString(vv.String()) if err != nil { return reflect.Value{}, err } mm.SetMapIndex(indirectInterface(key), reflect.ValueOf(nv)) } m.SetMapIndex(reflect.ValueOf(keyPlain), mm) case reflect.Slice: slice := reflect.MakeSlice(reflect.SliceOf(adapter.Type()), v.Len(), v.Cap()) for i := 0; i < v.Len(); i++ { vv := indirectInterface(v.Index(i)) nv, err := adapter.FromString(vv.String()) if err != nil { return reflect.Value{}, err } slice.Index(i).Set(reflect.ValueOf(nv)) } m.SetMapIndex(reflect.ValueOf(keyPlain), slice) default: nv, err := adapter.FromString(v.String()) if err != nil { return reflect.Value{}, err } m.SetMapIndex(reflect.ValueOf(keyPlain), reflect.ValueOf(nv)) } m.SetMapIndex(key, reflect.Value{}) } return m, nil } var ( interfaceMapType = reflect.TypeOf(make(map[string]interface{})) interfaceSliceType = reflect.TypeOf([]interface{}{}) stringType = reflect.TypeOf("") ) func (c *Codec) toTypedMap(mi interface{}) (interface{}, error) { mv := reflect.ValueOf(mi) if mv.Kind() != reflect.Map || mv.Type().Key().Kind() != reflect.String { return nil, errors.New("must provide a map with string keys") } m := reflect.MakeMap(interfaceMapType) for _, key := range mv.MapKeys() { v := indirectInterface(mv.MapIndex(key)) switch v.Kind() { case reflect.Map: if wrapper, found := c.typeAdaptersMap[v.Type().Elem()]; found { mm := reflect.MakeMap(interfaceMapType) for _, key := range v.MapKeys() { mm.SetMapIndex(key, reflect.ValueOf(wrapper.Wrap(v.MapIndex(key).Interface()))) } m.SetMapIndex(c.newKey(key, wrapper), mm) } else { nested, err := c.toTypedMap(v.Interface()) if err != nil { return nil, err } m.SetMapIndex(key, reflect.ValueOf(nested)) } continue case reflect.Slice: if adapter, found := c.typeAdaptersMap[v.Type().Elem()]; found { slice := reflect.MakeSlice(interfaceSliceType, v.Len(), v.Cap()) for i := 0; i < v.Len(); i++ { slice.Index(i).Set(reflect.ValueOf(adapter.Wrap(v.Index(i).Interface()))) } m.SetMapIndex(c.newKey(key, adapter), slice) continue } } if adapter, found := c.typeAdaptersMap[v.Type()]; found { m.SetMapIndex(c.newKey(key, adapter), reflect.ValueOf(adapter.Wrap(v.Interface()))) } else { m.SetMapIndex(key, v) } } return m.Interface(), nil } func (c *Codec) toStringMap(mi reflect.Value) (reflect.Value, error) { elemType := mi.Type().Elem() m := reflect.MakeMap(reflect.MapOf(stringType, elemType)) for _, key := range mi.MapKeys() { key = indirectInterface(key) if key.Kind() != reflect.String { return reflect.Value{}, errors.New("this library supports only string keys in maps") } vv := mi.MapIndex(key) m.SetMapIndex(reflect.ValueOf(key.String()), vv) } return m, nil } // MarshalUnmarshaler is the interface that must be implemented if you want to // add support for more than JSON to this codec. type MarshalUnmarshaler interface { Marshal(v interface{}) ([]byte, error) Unmarshal(b []byte, v interface{}) error } type jsonMarshaler int func (jsonMarshaler) Marshal(v interface{}) ([]byte, error) { return json.Marshal(v) } func (jsonMarshaler) Unmarshal(b []byte, v interface{}) error { return json.Unmarshal(b, v) } // Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931 func indirectInterface(v reflect.Value) reflect.Value { if v.Kind() != reflect.Interface { return v } if v.IsNil() { return reflect.Value{} } return v.Elem() } tmc-0.5.1/codec_test.go000066400000000000000000000102531353327231700147520ustar00rootroot00000000000000// Copyright © 2019 Bjørn Erik Pedersen . // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package tmc import ( "encoding/json" "math/big" "testing" "time" yaml "gopkg.in/yaml.v2" qt "github.com/frankban/quicktest" "github.com/google/go-cmp/cmp" ) func TestRoundtrip(t *testing.T) { c := qt.New(t) src := newTestMap() for _, test := range []struct { name string options []Option dataAssert func(c *qt.C, data string) }{ {"Default", nil, func(c *qt.C, data string) { c.Assert(data, qt.Contains, `{"nested":{"vduration|time.Duration":"5s"`) }}, {"Custom type separator", []Option{ WithTypeSep("TYPE:"), }, func(c *qt.C, data string) { c.Assert(data, qt.Contains, "TYPE:") }, }, {"YAML", []Option{ WithMarshalUnmarshaler(new(yamlMarshaler)), }, func(c *qt.C, data string) { c.Assert(data, qt.Contains, "vduration|time.Duration: 3s") }, }, {"JSON indent", []Option{ WithMarshalUnmarshaler(new(jsonMarshalerIndent)), }, func(c *qt.C, data string) { }, }, } { test := test c.Run(test.name, func(c *qt.C) { c.Parallel() codec, err := New(test.options...) c.Assert(err, qt.IsNil) data, err := codec.Marshal(src) c.Assert(err, qt.IsNil) //c.Log(string(data)) if test.dataAssert != nil { test.dataAssert(c, string(data)) } dst := make(map[string]interface{}) c.Assert(codec.Unmarshal(data, &dst), qt.IsNil) c.Assert(dst, eq, src) }) } } func TestErrors(t *testing.T) { c := qt.New(t) codec, err := New() c.Assert(err, qt.IsNil) marshal := func(v interface{}) error { _, err := codec.Marshal(v) return err } // OK c.Assert(marshal(map[string]interface{}{"32": "a"}), qt.IsNil) c.Assert(marshal(map[string]int{"32": 32}), qt.IsNil) // Should fail c.Assert(marshal([]string{"a"}), qt.Not(qt.IsNil)) c.Assert(marshal(map[int]interface{}{32: "a"}), qt.Not(qt.IsNil)) c.Assert(marshal(map[string]interface{}{"a": map[int]string{32: "32"}}), qt.Not(qt.IsNil)) } func BenchmarkCodec(b *testing.B) { b.Run("JSON regular", func(b *testing.B) { b.StopTimer() mi := newTestMap() b.StartTimer() for i := 0; i < b.N; i++ { data, err := json.Marshal(mi) if err != nil { b.Fatal(err) } m := make(map[string]interface{}) if err := json.Unmarshal(data, &m); err != nil { b.Fatal(err) } } }) b.Run("JSON typed", func(b *testing.B) { b.StopTimer() mi := newTestMap() c, err := New() if err != nil { b.Fatal(err) } b.StartTimer() for i := 0; i < b.N; i++ { data, err := c.Marshal(mi) if err != nil { b.Fatal(err) } m := make(map[string]interface{}) if err := c.Unmarshal(data, &m); err != nil { b.Fatal(err) } } }) } func newTestMap() map[string]interface{} { return map[string]interface{}{ "vstring": "Hello1", "vstring|": "Hello2", "vstring|foo": "Hello3", // Numbers "vint": 32, "vfloat64": float64(3.14159), "vrat": big.NewRat(1, 2), // Time "vtime": time.Now(), "vduration": 3 * time.Second, "vsliceint": []int{1, 3, 4}, "nested": map[string]interface{}{ "vint": 55, "vduration": 5 * time.Second, }, "nested-typed-int": map[string]int{ "vint": 42, }, "nested-typed-duration": map[string]time.Duration{ "v1": 5 * time.Second, "v2": 10 * time.Second, }, } } var eq = qt.CmpEquals( cmp.Comparer( func(v1, v2 *big.Rat) bool { return v1.RatString() == v2.RatString() }, ), cmp.Comparer(func(v1, v2 time.Time) bool { // UnmarshalText always create times with no monotonic clock reading, // so we cannot compare with ==. // TODO(bep) improve this return v1.Unix() == v2.Unix() }), ) type yamlMarshaler int func (yamlMarshaler) Marshal(v interface{}) ([]byte, error) { return yaml.Marshal(v) } func (yamlMarshaler) Unmarshal(b []byte, v interface{}) error { return yaml.Unmarshal(b, v) } // Useful for debugging type jsonMarshalerIndent int func (jsonMarshalerIndent) Marshal(v interface{}) ([]byte, error) { return json.MarshalIndent(v, "", " ") } func (jsonMarshalerIndent) Unmarshal(b []byte, v interface{}) error { return json.Unmarshal(b, v) } tmc-0.5.1/codecov.yml000066400000000000000000000002161353327231700144520ustar00rootroot00000000000000coverage: status: project: default: target: auto threshold: 0.5% patch: off comment: require_changes: true tmc-0.5.1/examples_test.go000066400000000000000000000023761353327231700155220ustar00rootroot00000000000000// Copyright © 2019 Bjørn Erik Pedersen . // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package tmc_test import ( "fmt" "log" "github.com/bep/tmc" yaml "gopkg.in/yaml.v2" ) func Example() { m1 := map[string]interface{}{"num": 42} c, err := tmc.New() if err != nil { log.Fatal(err) } data, err := c.Marshal(m1) if err != nil { log.Fatal(err) } m2 := make(map[string]interface{}) err = c.Unmarshal(data, &m2) if err != nil { log.Fatal(err) } num := m2["num"] fmt.Printf("%v (%T)", num, num) // Output: 42 (int) } func ExampleWithMarshalUnmarshaler() { m1 := map[string]interface{}{"num": 42} c, err := tmc.New(tmc.WithMarshalUnmarshaler(new(yamlMarshaler))) if err != nil { log.Fatal(err) } data, err := c.Marshal(m1) if err != nil { log.Fatal(err) } m2 := make(map[string]interface{}) err = c.Unmarshal(data, &m2) if err != nil { log.Fatal(err) } num := m2["num"] fmt.Printf("%v (%T)", num, num) // Output: 42 (int) } type yamlMarshaler int func (yamlMarshaler) Marshal(v interface{}) ([]byte, error) { return yaml.Marshal(v) } func (yamlMarshaler) Unmarshal(b []byte, v interface{}) error { return yaml.Unmarshal(b, v) } tmc-0.5.1/go.mod000066400000000000000000000002601353327231700134120ustar00rootroot00000000000000module github.com/bep/tmc go 1.12 require ( github.com/bep/debounce v1.2.0 github.com/frankban/quicktest v1.4.1 github.com/google/go-cmp v0.3.0 gopkg.in/yaml.v2 v2.2.2 ) tmc-0.5.1/go.sum000066400000000000000000000023571353327231700134500ustar00rootroot00000000000000github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo= github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg= github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=