Repository: goccy/go-yaml
Pull request #40 (2019-11-13 01:33:03)

Add support for `json` struct tags
PR to work with #39 


Patch:
diff --git a/README.md b/README.md
index 832c974..772322a 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/struct.go b/struct.go
index 5040031..2c6015d 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
 	}


Test patch:
diff --git a/decode_test.go b/decode_test.go
index 7e992fe..747fb2f 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
+}