diff --git a/bencode/struct.go b/bencode/struct.go index c6967c7..59c7b71 100644 --- a/bencode/struct.go +++ b/bencode/struct.go @@ -424,7 +424,7 @@ func bencodeKey(field reflect.StructField) (key string) { } } } - return + return key } func writeStruct(w io.Writer, val reflect.Value) (err error) { @@ -440,7 +440,11 @@ func writeStruct(w io.Writer, val reflect.Value) (err error) { for i := 0; i < numFields; i++ { field := typ.Field(i) - svList[i].key = bencodeKey(field) + key, opts := parseTag(bencodeKey(field)) + if key == "-" || opts.Contains("omitempty") && isEmptyValue(val.Field(i)) { + continue + } + svList[i].key = key svList[i].value = val.Field(i) } @@ -456,6 +460,59 @@ func writeStruct(w io.Writer, val reflect.Value) (err error) { return } +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +// tagOptions is the string following a comma in a struct field's "json" +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, tagOptions("") +} + +// Contains reports whether a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} + func writeValue(w io.Writer, val reflect.Value) (err error) { if !val.IsValid() { err = errors.New("Can't write null value") diff --git a/bencode/struct_test.go b/bencode/struct_test.go index 4323c82..16f3aab 100644 --- a/bencode/struct_test.go +++ b/bencode/struct_test.go @@ -1,12 +1,14 @@ package bencode import ( + "bytes" "testing" ) type testStruct struct { Field1 string `bencode:"my field1"` Field2 int64 `bencode:"my field2"` + Field3 int64 `bencode:"my field3,omitempty"` } type testOldTag struct { @@ -15,12 +17,16 @@ type testOldTag struct { } func TestMarshalling(t *testing.T) { - ts1 := testStruct{"foo", 123456} + ts1 := testStruct{Field1: "foo", Field2: 123456} buf, err := Marshal(ts1) if err != nil { t.Fatal(err) } + if bytes.Contains(buf, []byte("omitempty")) || bytes.Contains(buf, []byte("field3")) { + t.Errorf("should not have the string 'omitempty' or 'field3' in %q", buf) + } + ts2 := testStruct{} err = Unmarshal(buf, &ts2) if err != nil { diff --git a/torrent/file_test.go b/torrent/file_test.go index fdd3436..0cdeb11 100644 --- a/torrent/file_test.go +++ b/torrent/file_test.go @@ -1,6 +1,7 @@ package torrent import ( + "bytes" "github.com/vbatts/go-bt/bencode" "testing" ) @@ -19,6 +20,10 @@ func TestFileMarshal(t *testing.T) { t.Fatal(err) } + if bytes.Contains(buf, []byte("omitempty")) || bytes.Contains(buf, []byte("created by")) { + t.Errorf("should not have the string 'omitempty' or 'created by' in %q", buf) + } + f2 := File{} err = bencode.Unmarshal(buf, &f2) if err != nil {