|
|
@@ -1,9 +1,20 @@
|
|
|
-package yaml
|
|
|
+// Package yaml provides a wrapper around go-yaml designed to enable a better
|
|
|
+// way of handling YAML when marshaling to and from structs.
|
|
|
+//
|
|
|
+// In short, this package first converts YAML to JSON using go-yaml and then
|
|
|
+// uses json.Marshal and json.Unmarshal to convert to or from the struct. This
|
|
|
+// means that it effectively reuses the JSON struct tags as well as the custom
|
|
|
+// JSON methods MarshalJSON and UnmarshalJSON unlike go-yaml.
|
|
|
+//
|
|
|
+// See also http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang
|
|
|
+//
|
|
|
+package yaml // import "github.com/ghodss/yaml"
|
|
|
|
|
|
import (
|
|
|
"bytes"
|
|
|
"encoding/json"
|
|
|
"fmt"
|
|
|
+ "io"
|
|
|
"reflect"
|
|
|
"strconv"
|
|
|
|
|
|
@@ -26,15 +37,30 @@ func Marshal(o interface{}) ([]byte, error) {
|
|
|
return y, nil
|
|
|
}
|
|
|
|
|
|
-// Converts YAML to JSON then uses JSON to unmarshal into an object.
|
|
|
-func Unmarshal(y []byte, o interface{}) error {
|
|
|
+// JSONOpt is a decoding option for decoding from JSON format.
|
|
|
+type JSONOpt func(*json.Decoder) *json.Decoder
|
|
|
+
|
|
|
+// Unmarshal converts YAML to JSON then uses JSON to unmarshal into an object,
|
|
|
+// optionally configuring the behavior of the JSON unmarshal.
|
|
|
+func Unmarshal(y []byte, o interface{}, opts ...JSONOpt) error {
|
|
|
+ return unmarshal(yaml.Unmarshal, y, o, opts)
|
|
|
+}
|
|
|
+
|
|
|
+// UnmarshalStrict is like Unmarshal except that any mapping keys that are
|
|
|
+// duplicates will result in an error.
|
|
|
+// To also be strict about unknown fields, add the DisallowUnknownFields option.
|
|
|
+func UnmarshalStrict(y []byte, o interface{}, opts ...JSONOpt) error {
|
|
|
+ return unmarshal(yaml.UnmarshalStrict, y, o, opts)
|
|
|
+}
|
|
|
+
|
|
|
+func unmarshal(f func(in []byte, out interface{}) (err error), y []byte, o interface{}, opts []JSONOpt) error {
|
|
|
vo := reflect.ValueOf(o)
|
|
|
- j, err := yamlToJSON(y, &vo)
|
|
|
+ j, err := yamlToJSON(y, &vo, f)
|
|
|
if err != nil {
|
|
|
return fmt.Errorf("error converting YAML to JSON: %v", err)
|
|
|
}
|
|
|
|
|
|
- err = json.Unmarshal(j, o)
|
|
|
+ err = jsonUnmarshal(bytes.NewReader(j), o, opts...)
|
|
|
if err != nil {
|
|
|
return fmt.Errorf("error unmarshaling JSON: %v", err)
|
|
|
}
|
|
|
@@ -42,6 +68,21 @@ func Unmarshal(y []byte, o interface{}) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// jsonUnmarshal unmarshals the JSON byte stream from the given reader into the
|
|
|
+// object, optionally applying decoder options prior to decoding. We are not
|
|
|
+// using json.Unmarshal directly as we want the chance to pass in non-default
|
|
|
+// options.
|
|
|
+func jsonUnmarshal(r io.Reader, o interface{}, opts ...JSONOpt) error {
|
|
|
+ d := json.NewDecoder(r)
|
|
|
+ for _, opt := range opts {
|
|
|
+ d = opt(d)
|
|
|
+ }
|
|
|
+ if err := d.Decode(&o); err != nil {
|
|
|
+ return fmt.Errorf("while decoding JSON: %v", err)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
// Convert JSON to YAML.
|
|
|
func JSONToYAML(j []byte) ([]byte, error) {
|
|
|
// Convert the JSON to an object.
|
|
|
@@ -60,8 +101,8 @@ func JSONToYAML(j []byte) ([]byte, error) {
|
|
|
return yaml.Marshal(jsonObj)
|
|
|
}
|
|
|
|
|
|
-// Convert YAML to JSON. Since JSON is a subset of YAML, passing JSON through
|
|
|
-// this method should be a no-op.
|
|
|
+// YAMLToJSON converts YAML to JSON. Since JSON is a subset of YAML,
|
|
|
+// passing JSON through this method should be a no-op.
|
|
|
//
|
|
|
// Things YAML can do that are not supported by JSON:
|
|
|
// * In YAML you can have binary and null keys in your maps. These are invalid
|
|
|
@@ -70,14 +111,22 @@ func JSONToYAML(j []byte) ([]byte, error) {
|
|
|
// use binary data with this library, encode the data as base64 as usual but do
|
|
|
// not use the !!binary tag in your YAML. This will ensure the original base64
|
|
|
// encoded data makes it all the way through to the JSON.
|
|
|
+//
|
|
|
+// For strict decoding of YAML, use YAMLToJSONStrict.
|
|
|
func YAMLToJSON(y []byte) ([]byte, error) {
|
|
|
- return yamlToJSON(y, nil)
|
|
|
+ return yamlToJSON(y, nil, yaml.Unmarshal)
|
|
|
+}
|
|
|
+
|
|
|
+// YAMLToJSONStrict is like YAMLToJSON but enables strict YAML decoding,
|
|
|
+// returning an error on any duplicate field names.
|
|
|
+func YAMLToJSONStrict(y []byte) ([]byte, error) {
|
|
|
+ return yamlToJSON(y, nil, yaml.UnmarshalStrict)
|
|
|
}
|
|
|
|
|
|
-func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) {
|
|
|
+func yamlToJSON(y []byte, jsonTarget *reflect.Value, yamlUnmarshal func([]byte, interface{}) error) ([]byte, error) {
|
|
|
// Convert the YAML to an object.
|
|
|
var yamlObj interface{}
|
|
|
- err := yaml.Unmarshal(y, &yamlObj)
|
|
|
+ err := yamlUnmarshal(y, &yamlObj)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
@@ -85,7 +134,7 @@ func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) {
|
|
|
// YAML objects are not completely compatible with JSON objects (e.g. you
|
|
|
// can have non-string keys in YAML). So, convert the YAML-compatible object
|
|
|
// to a JSON-compatible object, failing with an error if irrecoverable
|
|
|
- // incompatibilties happen along the way.
|
|
|
+ // incompatibilities happen along the way.
|
|
|
jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget)
|
|
|
if err != nil {
|
|
|
return nil, err
|