Skip to content

Commit

Permalink
Add support for "json" struct field tag
Browse files Browse the repository at this point in the history
To unmarshal a CBOR map into a struct, unmarshal matches CBOR map keys to the
keys in the following priority:
    1. "cbor" key in struct field tag,
    2. "json" key in struct field tag,
    3. struct field name.

Marshalling a struct follows the same rule.

// Field appears in CBOR as key "a".
Field int `cbor:"a"`

// Field appears in CBOR as key "a".
Field int `json:"a"`

// Field appears in CBOR as key "b".
Field int `json:"a" cbor:"b"`

// Field is ignored.
Field int `json:"a" cbor:"-"`

// Field appears in CBOR as key "Field".
Field int
  • Loading branch information
Faye Amacker committed Sep 11, 2019
1 parent 799c64b commit 8117beb
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Library size comparison (linux_amd64, Go 1.12):
* Decode slices, maps, and structs in-place.
* Decode into struct with field name case-insensitive match.
* Support canonical CBOR encoding for map/struct.
* Support struct field format tags under "cbor" key.
* Support both "cbor" and "json" keys for struct field format tags.
* Encode anonymous struct fields by `json` package struct fields visibility rules.
* Encode and decode nil slice/map/pointer/interface values correctly.
* Encode and decode indefinite length bytes/string/array/map (["streaming"](https://tools.ietf.org/html/rfc7049#section-2.2)).
Expand Down
11 changes: 8 additions & 3 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -975,9 +975,14 @@ func isHashableKind(k reflect.Kind) bool {
// entries. Unmarshal stores key-value pairs from the CBOR map into Go map.
//
// To unmarshal a CBOR map into a struct, Unmarshal matches CBOR map keys to the
// keys used by Marshal (either the struct field name or its tag), preferring an
// exact match but also accepting a case-insensitive match. Map keys which
// don't have a corresponding struct field are ignored.
// keys in the following priority:
//
// 1. "cbor" key in struct field tag,
// 2. "json" key in struct field tag,
// 3. struct field name.
//
// Unmarshal prefers an exact match but also accepts a case-insensitive match.
// Map keys which don't have a corresponding struct field are ignored.
//
// To unmarshal a CBOR text string into a time.Time value, Unmarshal parses text
// string formatted in RFC3339. To unmarshal a CBOR integer/float into a
Expand Down
66 changes: 66 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1242,3 +1242,69 @@ func TestDecodeTime(t *testing.T) {
})
}
}

func TestUnmarshalStructTag1(t *testing.T) {
type strc struct {
A string `cbor:"a"`
B string `cbor:"b"`
C string `cbor:"c"`
}
want := strc{
A: "A",
B: "B",
C: "C",
}
cborData := hexDecode("a3616161416162614261636143") // {"a":"A", "b":"B", "c":"C"}

var v strc
if err := cbor.Unmarshal(cborData, &v); err != nil {
t.Errorf("Unmarshal(0x%0x) returns error %v", cborData, err)
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Unmarshal(0x%0x) = %v (%T), want %v (%T)", cborData, v, v, want, want)
}
}

func TestUnmarshalStructTag2(t *testing.T) {
type strc struct {
A string `json:"a"`
B string `json:"b"`
C string `json:"c"`
}
want := strc{
A: "A",
B: "B",
C: "C",
}
cborData := hexDecode("a3616161416162614261636143") // {"a":"A", "b":"B", "c":"C"}

var v strc
if err := cbor.Unmarshal(cborData, &v); err != nil {
t.Errorf("Unmarshal(0x%0x) returns error %v", cborData, err)
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Unmarshal(0x%0x) = %v (%T), want %v (%T)", cborData, v, v, want, want)
}
}

func TestUnmarshalStructTag3(t *testing.T) {
type strc struct {
A string `json:"x" cbor:"a"`
B string `json:"y" cbor:"b"`
C string `json:"z"`
}
want := strc{
A: "A",
B: "B",
C: "C",
}
cborData := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}

var v strc
if err := cbor.Unmarshal(cborData, &v); err != nil {
t.Errorf("Unmarshal(0x%0x) returns error %v", cborData, err)
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Unmarshal(0x%0x) = %+v (%T), want %+v (%T)", cborData, v, v, want, want)
}
}
3 changes: 2 additions & 1 deletion encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,8 @@ func isEmptyValue(v reflect.Value) bool {
//
// Marshal supports format string stored under the "cbor" key in the struct
// field's tag. CBOR format string can specify the name of the field, "omitempty"
// option, and special case "-" for field omission.
// option, and special case "-" for field omission. if "cbor" key is absent,
// Marshal uses "json" key.
//
// Anonymous struct fields are usually marshalled as if their exported fields
// were fields in the outer struct. Marshal follows the same struct fields
Expand Down
60 changes: 60 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1100,3 +1100,63 @@ func parseTime(layout string, value string) time.Time {
}
return tm
}

func TestMarshalStructTag1(t *testing.T) {
type strc struct {
A string `cbor:"a"`
B string `cbor:"b"`
C string `cbor:"c"`
}
v := strc{
A: "A",
B: "B",
C: "C",
}
want := hexDecode("a3616161416162614261636143") // {"a":"A", "b":"B", "c":"C"}

if b, err := cbor.Marshal(v, cbor.EncOptions{Canonical: true}); err != nil {
t.Errorf("Marshal(%+v) returns error %v", v, err)
} else if !bytes.Equal(b, want) {
t.Errorf("Marshal(%+v) = %v, want %v", v, b, want)
}
}

func TestMarshalStructTag2(t *testing.T) {
type strc struct {
A string `json:"a"`
B string `json:"b"`
C string `json:"c"`
}
v := strc{
A: "A",
B: "B",
C: "C",
}
want := hexDecode("a3616161416162614261636143") // {"a":"A", "b":"B", "c":"C"}

if b, err := cbor.Marshal(v, cbor.EncOptions{Canonical: true}); err != nil {
t.Errorf("Marshal(%+v) returns error %v", v, err)
} else if !bytes.Equal(b, want) {
t.Errorf("Marshal(%+v) = %v, want %v", v, b, want)
}
}

func TestMarshalStructTag3(t *testing.T) {
type strc struct {
A string `json:"x" cbor:"a"`
B string `json:"y" cbor:"b"`
C string `json:"z"`
}
v := strc{
A: "A",
B: "B",
C: "C",
}
want := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}

if b, err := cbor.Marshal(v, cbor.EncOptions{Canonical: true}); err != nil {
t.Errorf("Marshal(%+v) returns error %v", v, err)
} else if !bytes.Equal(b, want) {
t.Errorf("Marshal(%+v) = %v, want %v", v, b, want)
}
}
46 changes: 46 additions & 0 deletions stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,29 @@ func TestDecoderUnmarshalTypeError(t *testing.T) {
}
}

func TestDecoderStructTag(t *testing.T) {
type strc struct {
A string `json:"x" cbor:"a"`
B string `json:"y" cbor:"b"`
C string `json:"z"`
}
want := strc{
A: "A",
B: "B",
C: "C",
}
cborData := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}

var v strc
dec := cbor.NewDecoder(bytes.NewReader(cborData))
if err := dec.Decode(&v); err != nil {
t.Errorf("Decode() returns error %v", err)
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Decode() = %+v (%T), want %+v (%T)", v, v, want, want)
}
}

func TestEncoder(t *testing.T) {
var want bytes.Buffer
var w bytes.Buffer
Expand Down Expand Up @@ -257,3 +280,26 @@ func TestIndefiniteMap(t *testing.T) {
t.Errorf("Encoding mismatch: got %v, want %v", w.Bytes(), want)
}
}

func TestEncoderStructTag(t *testing.T) {
type strc struct {
A string `json:"x" cbor:"a"`
B string `json:"y" cbor:"b"`
C string `json:"z"`
}
v := strc{
A: "A",
B: "B",
C: "C",
}
want := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}

var w bytes.Buffer
encoder := cbor.NewEncoder(&w, cbor.EncOptions{Canonical: true})
if err := encoder.Encode(v); err != nil {
t.Errorf("Encode(%+v) returns error %v", v, err)
}
if !bytes.Equal(w.Bytes(), want) {
t.Errorf("Encoding mismatch: got %v, want %v", w.Bytes(), want)
}
}
6 changes: 6 additions & 0 deletions structfields.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ func getFields(typ reflect.Type) fields {
if tag == "-" {
continue
}
if tag == "" {
tag = f.Tag.Get("json")
if tag == "-" {
continue
}
}

idx := make([]int, len(fieldIdx)+1)
copy(idx, fieldIdx)
Expand Down

0 comments on commit 8117beb

Please sign in to comment.