forked from ebhomengo/niki
1
0
Fork 0
niki/vendor/github.com/ghodss/yaml/yaml.go

506 lines
8.5 KiB
Go
Raw Normal View History

2024-05-14 13:07:09 +00:00
package yaml
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strconv"
"gopkg.in/yaml.v2"
)
// Marshals the object into JSON then converts JSON to YAML and returns the
2024-05-14 13:07:09 +00:00
// YAML.
2024-05-14 13:07:09 +00:00
func Marshal(o interface{}) ([]byte, error) {
2024-05-14 13:07:09 +00:00
j, err := json.Marshal(o)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return nil, fmt.Errorf("error marshaling into JSON: %v", err)
2024-05-14 13:07:09 +00:00
}
y, err := JSONToYAML(j)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return nil, fmt.Errorf("error converting JSON to YAML: %v", err)
2024-05-14 13:07:09 +00:00
}
return y, nil
2024-05-14 13:07:09 +00:00
}
// Converts YAML to JSON then uses JSON to unmarshal into an object.
2024-05-14 13:07:09 +00:00
func Unmarshal(y []byte, o interface{}) error {
2024-05-14 13:07:09 +00:00
vo := reflect.ValueOf(o)
2024-05-14 13:07:09 +00:00
j, err := yamlToJSON(y, &vo)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return fmt.Errorf("error converting YAML to JSON: %v", err)
2024-05-14 13:07:09 +00:00
}
err = json.Unmarshal(j, o)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return fmt.Errorf("error unmarshaling JSON: %v", err)
2024-05-14 13:07:09 +00:00
}
return nil
2024-05-14 13:07:09 +00:00
}
// Convert JSON to YAML.
2024-05-14 13:07:09 +00:00
func JSONToYAML(j []byte) ([]byte, error) {
2024-05-14 13:07:09 +00:00
// Convert the JSON to an object.
2024-05-14 13:07:09 +00:00
var jsonObj interface{}
2024-05-14 13:07:09 +00:00
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
2024-05-14 13:07:09 +00:00
// Go JSON library doesn't try to pick the right number type (int, float,
2024-05-14 13:07:09 +00:00
// etc.) when unmarshalling to interface{}, it just picks float64
2024-05-14 13:07:09 +00:00
// universally. go-yaml does go through the effort of picking the right
2024-05-14 13:07:09 +00:00
// number type, so we can preserve number type throughout this process.
2024-05-14 13:07:09 +00:00
err := yaml.Unmarshal(j, &jsonObj)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return nil, err
2024-05-14 13:07:09 +00:00
}
// Marshal this object into YAML.
2024-05-14 13:07:09 +00:00
return yaml.Marshal(jsonObj)
2024-05-14 13:07:09 +00:00
}
// Convert YAML to JSON. Since JSON is a subset of YAML, passing JSON through
2024-05-14 13:07:09 +00:00
// this method should be a no-op.
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// Things YAML can do that are not supported by JSON:
2024-06-14 08:41:36 +00:00
// - In YAML you can have binary and null keys in your maps. These are invalid
2024-06-14 08:41:36 +00:00
// in JSON. (int and float keys are converted to strings.)
2024-06-14 08:41:36 +00:00
// - Binary data in YAML with the !!binary tag is not supported. If you want to
2024-06-14 08:41:36 +00:00
// use binary data with this library, encode the data as base64 as usual but do
2024-06-14 08:41:36 +00:00
// not use the !!binary tag in your YAML. This will ensure the original base64
2024-06-14 08:41:36 +00:00
// encoded data makes it all the way through to the JSON.
2024-05-14 13:07:09 +00:00
func YAMLToJSON(y []byte) ([]byte, error) {
2024-05-14 13:07:09 +00:00
return yamlToJSON(y, nil)
2024-05-14 13:07:09 +00:00
}
func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) {
2024-05-14 13:07:09 +00:00
// Convert the YAML to an object.
2024-05-14 13:07:09 +00:00
var yamlObj interface{}
2024-05-14 13:07:09 +00:00
err := yaml.Unmarshal(y, &yamlObj)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return nil, err
2024-05-14 13:07:09 +00:00
}
// YAML objects are not completely compatible with JSON objects (e.g. you
2024-05-14 13:07:09 +00:00
// can have non-string keys in YAML). So, convert the YAML-compatible object
2024-05-14 13:07:09 +00:00
// to a JSON-compatible object, failing with an error if irrecoverable
2024-05-14 13:07:09 +00:00
// incompatibilties happen along the way.
2024-05-14 13:07:09 +00:00
jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return nil, err
2024-05-14 13:07:09 +00:00
}
// Convert this object to JSON and return the data.
2024-05-14 13:07:09 +00:00
return json.Marshal(jsonObj)
2024-05-14 13:07:09 +00:00
}
func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) {
2024-05-14 13:07:09 +00:00
var err error
// Resolve jsonTarget to a concrete value (i.e. not a pointer or an
2024-05-14 13:07:09 +00:00
// interface). We pass decodingNull as false because we're not actually
2024-05-14 13:07:09 +00:00
// decoding into the value, we're just checking if the ultimate target is a
2024-05-14 13:07:09 +00:00
// string.
2024-05-14 13:07:09 +00:00
if jsonTarget != nil {
2024-05-14 13:07:09 +00:00
ju, tu, pv := indirect(*jsonTarget, false)
2024-05-14 13:07:09 +00:00
// We have a JSON or Text Umarshaler at this level, so we can't be trying
2024-05-14 13:07:09 +00:00
// to decode into a string.
2024-05-14 13:07:09 +00:00
if ju != nil || tu != nil {
2024-05-14 13:07:09 +00:00
jsonTarget = nil
2024-05-14 13:07:09 +00:00
} else {
2024-05-14 13:07:09 +00:00
jsonTarget = &pv
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
// If yamlObj is a number or a boolean, check if jsonTarget is a string -
2024-05-14 13:07:09 +00:00
// if so, coerce. Else return normal.
2024-05-14 13:07:09 +00:00
// If yamlObj is a map or array, find the field that each key is
2024-05-14 13:07:09 +00:00
// unmarshaling to, and when you recurse pass the reflect.Value for that
2024-05-14 13:07:09 +00:00
// field back into this function.
2024-05-14 13:07:09 +00:00
switch typedYAMLObj := yamlObj.(type) {
2024-05-14 13:07:09 +00:00
case map[interface{}]interface{}:
2024-05-14 13:07:09 +00:00
// JSON does not support arbitrary keys in a map, so we must convert
2024-05-14 13:07:09 +00:00
// these keys to strings.
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// From my reading of go-yaml v2 (specifically the resolve function),
2024-05-14 13:07:09 +00:00
// keys can only have the types string, int, int64, float64, binary
2024-05-14 13:07:09 +00:00
// (unsupported), or null (unsupported).
2024-05-14 13:07:09 +00:00
strMap := make(map[string]interface{})
2024-05-14 13:07:09 +00:00
for k, v := range typedYAMLObj {
2024-05-14 13:07:09 +00:00
// Resolve the key to a string first.
2024-05-14 13:07:09 +00:00
var keyString string
2024-05-14 13:07:09 +00:00
switch typedKey := k.(type) {
2024-05-14 13:07:09 +00:00
case string:
2024-05-14 13:07:09 +00:00
keyString = typedKey
2024-05-14 13:07:09 +00:00
case int:
2024-05-14 13:07:09 +00:00
keyString = strconv.Itoa(typedKey)
2024-05-14 13:07:09 +00:00
case int64:
2024-05-14 13:07:09 +00:00
// go-yaml will only return an int64 as a key if the system
2024-05-14 13:07:09 +00:00
// architecture is 32-bit and the key's value is between 32-bit
2024-05-14 13:07:09 +00:00
// and 64-bit. Otherwise the key type will simply be int.
2024-05-14 13:07:09 +00:00
keyString = strconv.FormatInt(typedKey, 10)
2024-05-14 13:07:09 +00:00
case float64:
2024-05-14 13:07:09 +00:00
// Stolen from go-yaml to use the same conversion to string as
2024-05-14 13:07:09 +00:00
// the go-yaml library uses to convert float to string when
2024-05-14 13:07:09 +00:00
// Marshaling.
2024-05-14 13:07:09 +00:00
s := strconv.FormatFloat(typedKey, 'g', -1, 32)
2024-05-14 13:07:09 +00:00
switch s {
2024-05-14 13:07:09 +00:00
case "+Inf":
2024-05-14 13:07:09 +00:00
s = ".inf"
2024-05-14 13:07:09 +00:00
case "-Inf":
2024-05-14 13:07:09 +00:00
s = "-.inf"
2024-05-14 13:07:09 +00:00
case "NaN":
2024-05-14 13:07:09 +00:00
s = ".nan"
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
keyString = s
2024-05-14 13:07:09 +00:00
case bool:
2024-05-14 13:07:09 +00:00
if typedKey {
2024-05-14 13:07:09 +00:00
keyString = "true"
2024-05-14 13:07:09 +00:00
} else {
2024-05-14 13:07:09 +00:00
keyString = "false"
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
default:
2024-05-14 13:07:09 +00:00
return nil, fmt.Errorf("Unsupported map key of type: %s, key: %+#v, value: %+#v",
2024-05-14 13:07:09 +00:00
reflect.TypeOf(k), k, v)
2024-05-14 13:07:09 +00:00
}
// jsonTarget should be a struct or a map. If it's a struct, find
2024-05-14 13:07:09 +00:00
// the field it's going to map to and pass its reflect.Value. If
2024-05-14 13:07:09 +00:00
// it's a map, find the element type of the map and pass the
2024-05-14 13:07:09 +00:00
// reflect.Value created from that type. If it's neither, just pass
2024-05-14 13:07:09 +00:00
// nil - JSON conversion will error for us if it's a real issue.
2024-05-14 13:07:09 +00:00
if jsonTarget != nil {
2024-05-14 13:07:09 +00:00
t := *jsonTarget
2024-05-14 13:07:09 +00:00
if t.Kind() == reflect.Struct {
2024-05-14 13:07:09 +00:00
keyBytes := []byte(keyString)
2024-05-14 13:07:09 +00:00
// Find the field that the JSON library would use.
2024-05-14 13:07:09 +00:00
var f *field
2024-05-14 13:07:09 +00:00
fields := cachedTypeFields(t.Type())
2024-05-14 13:07:09 +00:00
for i := range fields {
2024-05-14 13:07:09 +00:00
ff := &fields[i]
2024-05-14 13:07:09 +00:00
if bytes.Equal(ff.nameBytes, keyBytes) {
2024-05-14 13:07:09 +00:00
f = ff
2024-05-14 13:07:09 +00:00
break
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
// Do case-insensitive comparison.
2024-05-14 13:07:09 +00:00
if f == nil && ff.equalFold(ff.nameBytes, keyBytes) {
2024-05-14 13:07:09 +00:00
f = ff
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
if f != nil {
2024-05-14 13:07:09 +00:00
// Find the reflect.Value of the most preferential
2024-05-14 13:07:09 +00:00
// struct field.
2024-05-14 13:07:09 +00:00
jtf := t.Field(f.index[0])
2024-05-14 13:07:09 +00:00
strMap[keyString], err = convertToJSONableObject(v, &jtf)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return nil, err
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
continue
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
} else if t.Kind() == reflect.Map {
2024-05-14 13:07:09 +00:00
// Create a zero value of the map's element type to use as
2024-05-14 13:07:09 +00:00
// the JSON target.
2024-05-14 13:07:09 +00:00
jtv := reflect.Zero(t.Type().Elem())
2024-05-14 13:07:09 +00:00
strMap[keyString], err = convertToJSONableObject(v, &jtv)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return nil, err
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
continue
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
strMap[keyString], err = convertToJSONableObject(v, nil)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return nil, err
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
return strMap, nil
2024-05-14 13:07:09 +00:00
case []interface{}:
2024-05-14 13:07:09 +00:00
// We need to recurse into arrays in case there are any
2024-05-14 13:07:09 +00:00
// map[interface{}]interface{}'s inside and to convert any
2024-05-14 13:07:09 +00:00
// numbers to strings.
// If jsonTarget is a slice (which it really should be), find the
2024-05-14 13:07:09 +00:00
// thing it's going to map to. If it's not a slice, just pass nil
2024-05-14 13:07:09 +00:00
// - JSON conversion will error for us if it's a real issue.
2024-05-14 13:07:09 +00:00
var jsonSliceElemValue *reflect.Value
2024-05-14 13:07:09 +00:00
if jsonTarget != nil {
2024-05-14 13:07:09 +00:00
t := *jsonTarget
2024-05-14 13:07:09 +00:00
if t.Kind() == reflect.Slice {
2024-05-14 13:07:09 +00:00
// By default slices point to nil, but we need a reflect.Value
2024-05-14 13:07:09 +00:00
// pointing to a value of the slice type, so we create one here.
2024-05-14 13:07:09 +00:00
ev := reflect.Indirect(reflect.New(t.Type().Elem()))
2024-05-14 13:07:09 +00:00
jsonSliceElemValue = &ev
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
// Make and use a new array.
2024-05-14 13:07:09 +00:00
arr := make([]interface{}, len(typedYAMLObj))
2024-05-14 13:07:09 +00:00
for i, v := range typedYAMLObj {
2024-05-14 13:07:09 +00:00
arr[i], err = convertToJSONableObject(v, jsonSliceElemValue)
2024-05-14 13:07:09 +00:00
if err != nil {
2024-05-14 13:07:09 +00:00
return nil, err
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
return arr, nil
2024-05-14 13:07:09 +00:00
default:
2024-05-14 13:07:09 +00:00
// If the target type is a string and the YAML type is a number,
2024-05-14 13:07:09 +00:00
// convert the YAML type to a string.
2024-05-14 13:07:09 +00:00
if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String {
2024-05-14 13:07:09 +00:00
// Based on my reading of go-yaml, it may return int, int64,
2024-05-14 13:07:09 +00:00
// float64, or uint64.
2024-05-14 13:07:09 +00:00
var s string
2024-05-14 13:07:09 +00:00
switch typedVal := typedYAMLObj.(type) {
2024-05-14 13:07:09 +00:00
case int:
2024-05-14 13:07:09 +00:00
s = strconv.FormatInt(int64(typedVal), 10)
2024-05-14 13:07:09 +00:00
case int64:
2024-05-14 13:07:09 +00:00
s = strconv.FormatInt(typedVal, 10)
2024-05-14 13:07:09 +00:00
case float64:
2024-05-14 13:07:09 +00:00
s = strconv.FormatFloat(typedVal, 'g', -1, 32)
2024-05-14 13:07:09 +00:00
case uint64:
2024-05-14 13:07:09 +00:00
s = strconv.FormatUint(typedVal, 10)
2024-05-14 13:07:09 +00:00
case bool:
2024-05-14 13:07:09 +00:00
if typedVal {
2024-05-14 13:07:09 +00:00
s = "true"
2024-05-14 13:07:09 +00:00
} else {
2024-05-14 13:07:09 +00:00
s = "false"
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
if len(s) > 0 {
2024-05-14 13:07:09 +00:00
yamlObj = interface{}(s)
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
return yamlObj, nil
2024-05-14 13:07:09 +00:00
}
return nil, nil
2024-05-14 13:07:09 +00:00
}