1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
<h2 align="center">Codec for a Typed Map</h2>
<p align="center">Provides round-trip serialization of typed Go maps.<p>
<p align="center"><a href="https://godoc.org/github.com/bep/tmc"><img src="https://godoc.org/github.com/bep/tmc?status.svg" /></a>
<a href="https://goreportcard.com/report/github.com/bep/tmc"><img src="https://goreportcard.com/badge/github.com/bep/tmc" /></a>
<a href="https://codecov.io/gh/bep/tmc"><img src="https://codecov.io/gh/bep/tmc/branch/master/graph/badge.svg" /></a>
<a href="https://github.com/bep/tmc/actions"><img src="https://action-badges.now.sh/bep/tmc?workflow=test" /></a></p>
### 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
```
|