Skip to content

Commit

Permalink
Merge pull request #40 from lestrrat-go/topic/json-struct-tags
Browse files Browse the repository at this point in the history
Add support for `json` struct tags
  • Loading branch information
goccy authored Nov 14, 2019
2 parents ae617c5 + 45fd347 commit 42c193f
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 2 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,40 @@ if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
}
```

To control marshal/unmarshal behavior, you can use the `yaml` tag

```go
yml := `---
foo: 1
bar: c
`
var v struct {
A int `yaml:"foo"`
B string `yaml:"bar"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
...
}
```

For convenience, we also accept the `json` tag. Note that not all options from
the `json` tag will have significance when parsing YAML documents. If both
tags exist, `yaml` tag will take precedence.

```go
yml := `---
foo: 1
bar: c
`
var v struct {
A int `json:"foo"`
B string `json:"bar"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
...
}
```

For custom marshal/unmarshaling, implement one of Bytes or Interface Marshaler/Unmarshaler. The difference is that while BytesMarshaler/BytesUnmarshaler behave like `encoding.json`, InterfaceMarshaler/InterfaceUnmarshaler behave like `gopkg.in/yaml.v2`.

Semantically both are the same, but they differ in performance. Because indentation matter in YAML, you cannot simply accept a valid YAML fragment from a Marshaler, and expect it to work when it is attached to the parent container's serialized form. Therefore when we receive use the BytesMarshaler, which returns []byte, we must decode it once to figure out how to make it work in the given context. If you use the InterfaceMarshaler, we can skip the decoding.
Expand Down
65 changes: 65 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package yaml_test
import (
"bytes"
"fmt"
"log"
"math"
"reflect"
"strings"
Expand Down Expand Up @@ -1058,3 +1059,67 @@ a:
t.Logf("%s", yaml.FormatError(err, false, true))
t.Logf("%s", yaml.FormatError(err, true, true))
}

func TestDecoder_JSONTags(t *testing.T) {
var v struct {
A string `json:"a_json"` // no YAML tag
B string `json:"b_json" yaml:"b_yaml"` // both tags
}

const src = `---
a_json: a_json_value
b_json: b_json_value
b_yaml: b_yaml_value
`
if err := yaml.NewDecoder(strings.NewReader(src)).Decode(&v); err != nil {
t.Fatalf(`parsing should succeed: %s`, err)
}

if v.A != "a_json_value" {
t.Fatalf("v.A should be `a_json_value`, got `%s`", v.A)
}

if v.B != "b_yaml_value" {
t.Fatalf("v.B should be `b_yaml_value`, got `%s`", v.B)
}
}

func Example_YAMLTags() {
yml := `---
foo: 1
bar: c
A: 2
B: d
`
var v struct {
A int `yaml:"foo" json:"A"`
B string `yaml:"bar" json:"B"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
log.Fatal(err)
}
fmt.Println(v.A)
fmt.Println(v.B)
// OUTPUT:
// 1
// c
}

func Example_JSONTags() {
yml := `---
foo: 1
bar: c
`
var v struct {
A int `json:"foo"`
B string `json:"bar"`
}
if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
log.Fatal(err)
}
fmt.Println(v.A)
fmt.Println(v.B)
// OUTPUT:
// 1
// c
}
15 changes: 13 additions & 2 deletions struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,19 @@ type StructField struct {
IsInline bool
}

func structField(field reflect.StructField) *StructField {
func getTag(field reflect.StructField) string {
// If struct tag `yaml` exist, use that. If no `yaml`
// exists, but `json` does, use that and try the best to
// adhere to its rules
tag := field.Tag.Get(StructTagName)
if tag == "" {
tag = field.Tag.Get(`json`)
}
return tag
}

func structField(field reflect.StructField) *StructField {
tag := getTag(field)
fieldName := strings.ToLower(field.Name)
options := strings.Split(tag, ",")
if len(options) > 0 {
Expand Down Expand Up @@ -73,7 +84,7 @@ func isIgnoredStructField(field reflect.StructField) bool {
// private field
return true
}
tag := field.Tag.Get(StructTagName)
tag := getTag(field)
if tag == "-" {
return true
}
Expand Down

0 comments on commit 42c193f

Please sign in to comment.