https://github.com/bep/tmc
Provides basic roundtrip JSON etc. encoding/decoding of a map[string]any with custom type adapters.
https://github.com/bep/tmc
go golang json
Last synced: about 1 year ago
JSON representation
Provides basic roundtrip JSON etc. encoding/decoding of a map[string]any with custom type adapters.
- Host: GitHub
- URL: https://github.com/bep/tmc
- Owner: bep
- License: mit
- Created: 2019-08-27T11:11:07.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2022-02-13T11:04:06.000Z (about 4 years ago)
- Last Synced: 2025-03-17T01:41:22.385Z (about 1 year ago)
- Topics: go, golang, json
- Language: Go
- Homepage:
- Size: 43.9 KB
- Stars: 9
- Watchers: 2
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
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 about (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-16 63921 16261 ns/op 6486 B/op 163 allocs/op
BenchmarkCodec/JSON_typed-16 31791 37396 ns/op 14538 B/op 387 allocs/op
```