Improve Set marshaling to add encoding support, and decoding to map (#2845)

The only support that marshal has for Set at the moment is decoding to
slice.
This commit is contained in:
Ben Kalman
2016-11-22 11:24:18 -08:00
committed by GitHub
parent 5e901b0924
commit 96d10ac29f
4 changed files with 322 additions and 31 deletions
+78 -19
View File
@@ -27,21 +27,28 @@ import (
// length to zero and then appends each element to the slice. If the Go slice
// was nil a new slice is created.
//
// To unmarshal a Noms list or set into a Go array, Unmarshal decodes Noms list
// To unmarshal a Noms list into a Go array, Unmarshal decodes Noms list
// elements into corresponding Go array elements.
//
// To unmarshal a Noms map into a Go map, Unmarshal decodes Noms key and values
// into corresponding Go array elements. If the Go map was nil a new map is
// created.
//
// When unmarshalling onto `interface{}` the following rules are used:
// - `types.Bool` -> `bool`
// - `types.List` -> `[]T`, where `T` is determined recursively using the same rules.
// - `types.Set` -> same as `types.List`
// - `types.Map` -> `map[T]V`, where `T` and `V` is determined recursively using the same rules.
// - `types.Number` -> `float64`
// - `types.String` -> `string`
// - `types.Union` -> `interface`
// To unmarshal Noms sets, it depends on the presence of a `noms:",set"` tag:
// - Without (default), Unmarshal decodes into corresponding Go list elements.
// - With, Unmarshal decodes into Go map keys corresponding to the set values,
// with values struct{}{}. Map values must have struct{} type.
//
// When unmarshalling onto interface{} the following rules are used:
// - types.Bool -> bool
// - types.List -> []T, where T is determined recursively using the same rules.
// - types.Set -> depends on `noms:",set"` annotation: without, same as
// types.List, with, same as types.Map.
// - types.Map -> map[T]V, where T and V is determined recursively using the
// same rules.
// - types.Number -> float64
// - types.String -> string
// - types.Union -> interface
// - Everything else an error
//
// Unmarshal returns an UnmarshalTypeMismatchError if:
@@ -66,7 +73,7 @@ func Unmarshal(v types.Value, out interface{}) (err error) {
return &InvalidUnmarshalError{reflect.TypeOf(out)}
}
rv = rv.Elem()
d := typeDecoder(rv.Type())
d := typeDecoder(rv.Type(), nomsTags{})
d(v, rv)
return
}
@@ -112,7 +119,7 @@ func overflowError(v types.Number, t reflect.Type) *UnmarshalTypeMismatchError {
type decoderFunc func(v types.Value, rv reflect.Value)
func typeDecoder(t reflect.Type) decoderFunc {
func typeDecoder(t reflect.Type, tags nomsTags) decoderFunc {
switch t.Kind() {
case reflect.Bool:
return boolDecoder
@@ -133,7 +140,10 @@ func typeDecoder(t reflect.Type) decoderFunc {
case reflect.Array:
return arrayDecoder(t)
case reflect.Map:
return mapDecoder(t)
if shouldMapDecodeFromSet(t, tags) {
return mapFromSetDecoder(t)
}
return mapDecoder(t, tags)
default:
panic(&UnsupportedTypeError{Type: t})
}
@@ -193,6 +203,10 @@ type decoderCacheT struct {
var decoderCache = &decoderCacheT{}
// Separate Set decoder cache because the same type with and without the
// `noms:",set"` tag decode differently (Set vs Map).
var setDecoderCache = &decoderCacheT{}
func (c *decoderCacheT) get(t reflect.Type) decoderFunc {
c.RLock()
defer c.RUnlock()
@@ -236,7 +250,7 @@ func structDecoder(t reflect.Type) decoderFunc {
fields = append(fields, decField{
name: tags.name,
decoder: typeDecoder(f.Type),
decoder: typeDecoder(f.Type, tags),
index: i,
})
}
@@ -307,7 +321,7 @@ func sliceDecoder(t reflect.Type) decoderFunc {
}
decoderCache.set(t, d)
decoder = typeDecoder(t.Elem())
decoder = typeDecoder(t.Elem(), nomsTags{})
return d
}
@@ -336,11 +350,43 @@ func arrayDecoder(t reflect.Type) decoderFunc {
}
decoderCache.set(t, d)
decoder = typeDecoder(t.Elem())
decoder = typeDecoder(t.Elem(), nomsTags{})
return d
}
func mapDecoder(t reflect.Type) decoderFunc {
func mapFromSetDecoder(t reflect.Type) decoderFunc {
d := setDecoderCache.get(t)
if d != nil {
return d
}
var decoder decoderFunc
d = func(v types.Value, rv reflect.Value) {
m := rv
if m.IsNil() {
m = reflect.MakeMap(t)
}
nomsSet, ok := v.(types.Set)
if !ok {
panic(&UnmarshalTypeMismatchError{v, t, `, field has "set" tag`})
}
nomsSet.IterAll(func(v types.Value) {
keyRv := reflect.New(t.Key()).Elem()
decoder(v, keyRv)
m.SetMapIndex(keyRv, reflect.New(t.Elem()).Elem())
})
rv.Set(m)
}
setDecoderCache.set(t, d)
decoder = typeDecoder(t.Key(), nomsTags{})
return d
}
func mapDecoder(t reflect.Type, tags nomsTags) decoderFunc {
d := decoderCache.get(t)
if d != nil {
return d
@@ -355,6 +401,12 @@ func mapDecoder(t reflect.Type) decoderFunc {
m = reflect.MakeMap(t)
}
// Special case decoding failure if it looks like the "set" tag is missing,
// because it's helpful.
if _, ok := v.(types.Set); ok && !tags.set {
panic(&UnmarshalTypeMismatchError{v, t, `, field missing "set" tag`})
}
nomsMap, ok := v.(types.Map)
if !ok {
panic(&UnmarshalTypeMismatchError{v, t, ""})
@@ -371,8 +423,8 @@ func mapDecoder(t reflect.Type) decoderFunc {
}
decoderCache.set(t, d)
keyDecoder = typeDecoder(t.Key())
valueDecoder = typeDecoder(t.Elem())
keyDecoder = typeDecoder(t.Key(), nomsTags{})
valueDecoder = typeDecoder(t.Elem(), nomsTags{})
return d
}
@@ -388,7 +440,7 @@ func interfaceDecoder(t reflect.Type) decoderFunc {
return func(v types.Value, rv reflect.Value) {
t := getGoTypeForNomsType(v.Type(), rv.Type(), v)
i := reflect.New(t).Elem()
typeDecoder(t)(v, i)
typeDecoder(t, nomsTags{})(v, i)
rv.Set(i)
}
}
@@ -420,3 +472,10 @@ func getGoTypeForNomsType(nt *types.Type, rt reflect.Type, v types.Value) reflec
panic(&UnmarshalTypeMismatchError{Value: v, Type: rt})
}
}
func shouldMapDecodeFromSet(rt reflect.Type, tags nomsTags) bool {
// map[T]struct{} `noms:,"set"`
return tags.set &&
rt.Elem().Kind() == reflect.Struct &&
rt.Elem().NumField() == 0
}
+86
View File
@@ -639,3 +639,89 @@ func TestDecodeOntoInterfaceStruct(t *testing.T) {
var i interface{}
assertDecodeErrorMessage(t, types.NewStruct("", types.StructData{}), &i, "Cannot unmarshal struct {} into Go value of type interface {}")
}
func TestDecodeSet(t *testing.T) {
assert := assert.New(t)
type T struct {
A map[int]struct{} `noms:",set"`
B map[int]struct{}
C map[string]struct{} `noms:",set"`
D map[string]struct{}
E []int
F []int
}
ns := types.NewStruct("T", types.StructData{
"a": types.NewSet(types.Number(0), types.Number(1), types.Number(2)),
"b": types.NewMap(types.Number(3), types.EmptyStruct, types.Number(4), types.EmptyStruct, types.Number(5), types.EmptyStruct),
"c": types.NewSet(types.String("0"), types.String("1"), types.String("2")),
"d": types.NewMap(types.String("3"), types.EmptyStruct, types.String("4"), types.EmptyStruct, types.String("5"), types.EmptyStruct),
"e": types.NewSet(types.Number(6), types.Number(7), types.Number(8)),
"f": types.NewList(types.Number(9), types.Number(10), types.Number(11)),
})
gs := T{}
assert.NoError(Unmarshal(ns, &gs))
assert.Equal(T{
A: map[int]struct{}{0: {}, 1: {}, 2: {}},
B: map[int]struct{}{3: {}, 4: {}, 5: {}},
C: map[string]struct{}{"0": {}, "1": {}, "2": {}},
D: map[string]struct{}{"3": {}, "4": {}, "5": {}},
E: []int{6, 7, 8},
F: []int{9, 10, 11},
}, gs)
}
func TestDecodeNamedSet(t *testing.T) {
assert := assert.New(t)
type T struct {
A map[int]struct{} `noms:"foo,set"`
}
ns := types.NewStruct("T", types.StructData{
"a": types.NewSet(types.Number(0)),
"foo": types.NewSet(types.Number(1)),
})
gs := T{}
assert.NoError(Unmarshal(ns, &gs))
assert.Equal(T{
map[int]struct{}{1: {}},
}, gs)
}
func TestDecodeSetWrongMapType(t *testing.T) {
assert := assert.New(t)
type T1 struct {
A map[int]int `noms:",set"`
}
err := Unmarshal(types.NewStruct("T1", types.StructData{
"a": types.NewSet(types.Number(0)),
}), &T1{})
assert.Error(err)
assert.Equal("Cannot unmarshal Set<Number> into Go value of type map[int]int", err.Error())
type T2 struct {
A map[int]struct{}
}
err = Unmarshal(types.NewStruct("T2", types.StructData{
"a": types.NewSet(types.Number(0)),
}), &T2{})
assert.Error(err)
assert.Equal(`Cannot unmarshal Set<Number> into Go value of type map[int]struct {}, field missing "set" tag`, err.Error())
type T3 struct {
A map[int]struct{} `noms:",set"`
}
err = Unmarshal(types.NewStruct("T3", types.StructData{
"a": types.NewMap(types.Number(0), types.EmptyStruct),
}), &T3{})
assert.Error(err)
assert.Equal(`Cannot unmarshal Map<Number, struct {}> into Go value of type map[int]struct {}, field has "set" tag`, err.Error())
}
+55 -12
View File
@@ -33,7 +33,8 @@ import (
//
// Slices and arrays are encoded as Noms types.List.
//
// Maps are encoded as Noms types.Map.
// Maps are encoded as Noms types.Map, or a types.Set if the value type is
// struct{} and the field is tagged with `noms:"set"`.
//
// Struct values are encoded as Noms structs (types.Struct). Each exported Go
// struct field becomes a member of the Noms struct unless
@@ -76,7 +77,7 @@ import (
// Noms values (values implementing types.Value) are copied over without any
// change.
//
// When marshalling `interface{}` the dynamic type is used.
// When marshalling interface{} the dynamic type is used.
//
// Go pointers, complex, function are not supported. Attempting to encode such a
// value causes Marshal to return an UnsupportedTypeError.
@@ -93,7 +94,7 @@ func Marshal(v interface{}) (nomsValue types.Value, err error) {
}
}()
rv := reflect.ValueOf(v)
encoder := typeEncoder(rv.Type(), nil)
encoder := typeEncoder(rv.Type(), nil, nomsTags{})
nomsValue = encoder(rv)
return
}
@@ -134,6 +135,7 @@ func (e *InvalidTagError) Error() string {
type nomsTags struct {
name string
omitEmpty bool
set bool
skip bool
}
@@ -166,7 +168,7 @@ func nomsValueEncoder(v reflect.Value) types.Value {
return v.Interface().(types.Value)
}
func typeEncoder(t reflect.Type, parentStructTypes []reflect.Type) encoderFunc {
func typeEncoder(t reflect.Type, parentStructTypes []reflect.Type, tags nomsTags) encoderFunc {
switch t.Kind() {
case reflect.Bool:
return boolEncoder
@@ -183,12 +185,15 @@ func typeEncoder(t reflect.Type, parentStructTypes []reflect.Type) encoderFunc {
case reflect.Slice, reflect.Array:
return listEncoder(t, parentStructTypes)
case reflect.Map:
if shouldMapEncodeAsSet(t, tags) {
return setEncoder(t, parentStructTypes)
}
return mapEncoder(t, parentStructTypes)
case reflect.Interface:
return func(v reflect.Value) types.Value {
// Get the dynamic type.
v2 := reflect.ValueOf(v.Interface())
return typeEncoder(v2.Type(), parentStructTypes)(v2)
return typeEncoder(v2.Type(), parentStructTypes, tags)(v2)
}
default:
panic(&UnsupportedTypeError{Type: t})
@@ -277,6 +282,10 @@ type encoderCacheT struct {
var encoderCache = &encoderCacheT{}
// Separate Set encoder cache because the same type with and without the
// `noms:",set"` tag encode differently (Set vs Map).
var setEncoderCache = &encoderCacheT{}
func (c *encoderCacheT) get(t reflect.Type) encoderFunc {
c.RLock()
defer c.RUnlock()
@@ -312,9 +321,15 @@ func getTags(f reflect.StructField) (tags nomsTags) {
panic(&InvalidTagError{"Invalid struct field name: " + tags.name})
}
if len(tagsSlice) > 1 {
// This is pretty simplistic but it is good enough for now.
tags.omitEmpty = tagsSlice[1] == "omitempty"
for i := 1; i < len(tagsSlice); i++ {
switch tag := tagsSlice[i]; tag {
case "omitempty":
tags.omitEmpty = true
case "set":
tags.set = true
default:
panic(&InvalidTagError{"Unrecognized tag: " + tag})
}
}
return
}
@@ -349,7 +364,7 @@ func typeFields(t reflect.Type, parentStructTypes []reflect.Type) (fields fieldS
fields = append(fields, field{
name: tags.name,
encoder: typeEncoder(f.Type, parentStructTypes),
encoder: typeEncoder(f.Type, parentStructTypes, tags),
index: i,
nomsType: nt,
omitEmpty: tags.omitEmpty,
@@ -437,7 +452,27 @@ func listEncoder(t reflect.Type, parentStructTypes []reflect.Type) encoderFunc {
}
encoderCache.set(t, e)
elemEncoder = typeEncoder(t.Elem(), parentStructTypes)
elemEncoder = typeEncoder(t.Elem(), parentStructTypes, nomsTags{})
return e
}
func setEncoder(t reflect.Type, parentStructTypes []reflect.Type) encoderFunc {
e := setEncoderCache.get(t)
if e != nil {
return e
}
var encoder encoderFunc
e = func(v reflect.Value) types.Value {
values := make([]types.Value, v.Len(), v.Len())
for i, k := range v.MapKeys() {
values[i] = encoder(k)
}
return types.NewSet(values...)
}
setEncoderCache.set(t, e)
encoder = typeEncoder(t.Key(), parentStructTypes, nomsTags{})
return e
}
@@ -460,7 +495,15 @@ func mapEncoder(t reflect.Type, parentStructTypes []reflect.Type) encoderFunc {
}
encoderCache.set(t, e)
keyEncoder = typeEncoder(t.Key(), parentStructTypes)
valueEncoder = typeEncoder(t.Elem(), parentStructTypes)
keyEncoder = typeEncoder(t.Key(), parentStructTypes, nomsTags{})
valueEncoder = typeEncoder(t.Elem(), parentStructTypes, nomsTags{})
return e
}
func shouldMapEncodeAsSet(t reflect.Type, tags nomsTags) bool {
d.PanicIfFalse(t.Kind() == reflect.Map)
// map[T]struct{} `noms:,"set"`
return tags.set &&
t.Elem().Kind() == reflect.Struct &&
t.Elem().NumField() == 0
}
+103
View File
@@ -503,6 +503,109 @@ func TestEncodeInterface(t *testing.T) {
).Equals(v))
}
func TestEncodeSet(t *testing.T) {
assert := assert.New(t)
v, err := Marshal(struct {
A map[int]struct{} `noms:",set"`
B map[int]struct{}
C map[int]string `noms:",set"`
D map[string]struct{} `noms:",set"`
E map[string]struct{}
F map[string]int `noms:",set"`
G []int `noms:",set"`
H string `noms:",set"`
}{
map[int]struct{}{0: {}, 1: {}, 2: {}},
map[int]struct{}{3: {}, 4: {}, 5: {}},
map[int]string{},
map[string]struct{}{"A": {}, "B": {}, "C": {}},
map[string]struct{}{"D": {}, "E": {}, "F": {}},
map[string]int{},
[]int{},
"",
})
assert.NoError(err)
s, ok := v.(types.Struct)
assert.True(ok)
expect := map[string]types.NomsKind{
"a": types.SetKind,
"b": types.MapKind,
"c": types.MapKind,
"d": types.SetKind,
"e": types.MapKind,
"f": types.MapKind,
"g": types.ListKind,
"h": types.StringKind,
}
for fieldName, kind := range expect {
assert.Equal(kind, s.Get(fieldName).Type().Kind())
}
// Test both the Set values are correct, and that the equivalent typed Map
// are correct in case the Set marshaling interferes with it.
a := s.Get("a").(types.Set)
assert.True(a.Has(types.Number(0)))
assert.True(a.Has(types.Number(1)))
assert.True(a.Has(types.Number(2)))
b := s.Get("b").(types.Map)
assert.True(b.Has(types.Number(3)))
assert.True(b.Has(types.Number(4)))
assert.True(b.Has(types.Number(5)))
d := s.Get("d").(types.Set)
assert.True(d.Has(types.String("A")))
assert.True(d.Has(types.String("B")))
assert.True(d.Has(types.String("C")))
e := s.Get("e").(types.Map)
assert.True(e.Has(types.String("D")))
assert.True(e.Has(types.String("E")))
assert.True(e.Has(types.String("F")))
}
func TestEncodeSetWithTags(t *testing.T) {
assert := assert.New(t)
v, err := Marshal(struct {
A map[int]struct{} `noms:"foo,set"`
B map[int]struct{} `noms:",omitempty,set"`
C map[int]struct{} `noms:"bar,omitempty,set"`
}{
A: map[int]struct{}{0: {}, 1: {}},
C: map[int]struct{}{2: {}, 3: {}},
})
assert.NoError(err)
s, ok := v.(types.Struct)
assert.True(ok)
_, ok = s.MaybeGet("a")
assert.False(ok)
_, ok = s.MaybeGet("b")
assert.False(ok)
_, ok = s.MaybeGet("c")
assert.False(ok)
foo, ok := s.Get("foo").(types.Set)
assert.True(ok)
assert.True(types.NewSet(types.Number(0), types.Number(1)).Equals(foo))
bar, ok := s.Get("bar").(types.Set)
assert.True(ok)
assert.True(types.NewSet(types.Number(2), types.Number(3)).Equals(bar))
}
func TestInvalidTag(t *testing.T) {
_, err := Marshal(struct {
F string `noms:",omitEmpty"`
}{"F"})
assert.Error(t, err)
assert.Equal(t, `Unrecognized tag: omitEmpty`, err.Error())
}
type TestInterface interface {
M()
}