diff --git a/README.md b/README.md index 832c9749..772322a1 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/decode_test.go b/decode_test.go index 7e992fec..747fb2f0 100644 --- a/decode_test.go +++ b/decode_test.go @@ -3,6 +3,7 @@ package yaml_test import ( "bytes" "fmt" + "log" "math" "reflect" "strings" @@ -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 +} diff --git a/struct.go b/struct.go index 5040031c..2c6015da 100644 --- a/struct.go +++ b/struct.go @@ -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 { @@ -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 }