yaml.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. /*
  2. Copyright 2021 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package yaml
  14. import (
  15. "bytes"
  16. "encoding/json"
  17. "fmt"
  18. "io"
  19. "reflect"
  20. "strconv"
  21. "go.yaml.in/yaml/v2"
  22. )
  23. // Marshal marshals obj into JSON using stdlib json.Marshal, and then converts JSON to YAML using JSONToYAML (see that method for more reference)
  24. func Marshal(obj interface{}) ([]byte, error) {
  25. jsonBytes, err := json.Marshal(obj)
  26. if err != nil {
  27. return nil, fmt.Errorf("error marshaling into JSON: %w", err)
  28. }
  29. return JSONToYAML(jsonBytes)
  30. }
  31. // JSONOpt is a decoding option for decoding from JSON format.
  32. type JSONOpt func(*json.Decoder) *json.Decoder
  33. // Unmarshal first converts the given YAML to JSON, and then unmarshals the JSON into obj. Options for the
  34. // standard library json.Decoder can be optionally specified, e.g. to decode untyped numbers into json.Number instead of float64, or to disallow unknown fields (but for that purpose, see also UnmarshalStrict). obj must be a non-nil pointer.
  35. //
  36. // Important notes about the Unmarshal logic:
  37. //
  38. // - Decoding is case-insensitive, unlike the rest of Kubernetes API machinery, as this is using the stdlib json library. This might be confusing to users.
  39. // - This decodes any number (although it is an integer) into a float64 if the type of obj is unknown, e.g. *map[string]interface{}, *interface{}, or *[]interface{}. This means integers above +/- 2^53 will lose precision when round-tripping. Make a JSONOpt that calls d.UseNumber() to avoid this.
  40. // - Duplicate fields, including in-case-sensitive matches, are ignored in an undefined order. Note that the YAML specification forbids duplicate fields, so this logic is more permissive than it needs to. See UnmarshalStrict for an alternative.
  41. // - Unknown fields, i.e. serialized data that do not map to a field in obj, are ignored. Use d.DisallowUnknownFields() or UnmarshalStrict to override.
  42. // - As per the YAML 1.1 specification, which yaml.v2 used underneath implements, literal 'yes' and 'no' strings without quotation marks will be converted to true/false implicitly.
  43. // - YAML non-string keys, e.g. ints, bools and floats, are converted to strings implicitly during the YAML to JSON conversion process.
  44. // - There are no compatibility guarantees for returned error values.
  45. func Unmarshal(yamlBytes []byte, obj interface{}, opts ...JSONOpt) error {
  46. return unmarshal(yamlBytes, obj, yaml.Unmarshal, opts...)
  47. }
  48. // UnmarshalStrict is similar to Unmarshal (please read its documentation for reference), with the following exceptions:
  49. //
  50. // - Duplicate fields in an object yield an error. This is according to the YAML specification.
  51. // - If obj, or any of its recursive children, is a struct, presence of fields in the serialized data unknown to the struct will yield an error.
  52. func UnmarshalStrict(yamlBytes []byte, obj interface{}, opts ...JSONOpt) error {
  53. return unmarshal(yamlBytes, obj, yaml.UnmarshalStrict, append(opts, DisallowUnknownFields)...)
  54. }
  55. // unmarshal unmarshals the given YAML byte stream into the given interface,
  56. // optionally performing the unmarshalling strictly
  57. func unmarshal(yamlBytes []byte, obj interface{}, unmarshalFn func([]byte, interface{}) error, opts ...JSONOpt) error {
  58. jsonTarget := reflect.ValueOf(obj)
  59. jsonBytes, err := yamlToJSONTarget(yamlBytes, &jsonTarget, unmarshalFn)
  60. if err != nil {
  61. return fmt.Errorf("error converting YAML to JSON: %w", err)
  62. }
  63. err = jsonUnmarshal(bytes.NewReader(jsonBytes), obj, opts...)
  64. if err != nil {
  65. return fmt.Errorf("error unmarshaling JSON: %w", err)
  66. }
  67. return nil
  68. }
  69. // jsonUnmarshal unmarshals the JSON byte stream from the given reader into the
  70. // object, optionally applying decoder options prior to decoding. We are not
  71. // using json.Unmarshal directly as we want the chance to pass in non-default
  72. // options.
  73. func jsonUnmarshal(reader io.Reader, obj interface{}, opts ...JSONOpt) error {
  74. d := json.NewDecoder(reader)
  75. for _, opt := range opts {
  76. d = opt(d)
  77. }
  78. if err := d.Decode(&obj); err != nil {
  79. return fmt.Errorf("while decoding JSON: %w", err)
  80. }
  81. return nil
  82. }
  83. // JSONToYAML converts JSON to YAML. Notable implementation details:
  84. //
  85. // - Duplicate fields, are case-sensitively ignored in an undefined order.
  86. // - The sequence indentation style is compact, which means that the "- " marker for a YAML sequence will be on the same indentation level as the sequence field name.
  87. // - Unlike Unmarshal, all integers, up to 64 bits, are preserved during this round-trip.
  88. func JSONToYAML(j []byte) ([]byte, error) {
  89. // Convert the JSON to an object.
  90. var jsonObj interface{}
  91. // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
  92. // Go JSON library doesn't try to pick the right number type (int, float,
  93. // etc.) when unmarshalling to interface{}, it just picks float64
  94. // universally. go-yaml does go through the effort of picking the right
  95. // number type, so we can preserve number type throughout this process.
  96. err := yaml.Unmarshal(j, &jsonObj)
  97. if err != nil {
  98. return nil, err
  99. }
  100. // Marshal this object into YAML.
  101. yamlBytes, err := yaml.Marshal(jsonObj)
  102. if err != nil {
  103. return nil, err
  104. }
  105. return yamlBytes, nil
  106. }
  107. // YAMLToJSON converts YAML to JSON. Since JSON is a subset of YAML,
  108. // passing JSON through this method should be a no-op.
  109. //
  110. // Some things YAML can do that are not supported by JSON:
  111. // - In YAML you can have binary and null keys in your maps. These are invalid
  112. // in JSON, and therefore int, bool and float keys are converted to strings implicitly.
  113. // - Binary data in YAML with the !!binary tag is not supported. If you want to
  114. // use binary data with this library, encode the data as base64 as usual but do
  115. // not use the !!binary tag in your YAML. This will ensure the original base64
  116. // encoded data makes it all the way through to the JSON.
  117. // - And more... read the YAML specification for more details.
  118. //
  119. // Notable about the implementation:
  120. //
  121. // - Duplicate fields are case-sensitively ignored in an undefined order. Note that the YAML specification forbids duplicate fields, so this logic is more permissive than it needs to. See YAMLToJSONStrict for an alternative.
  122. // - As per the YAML 1.1 specification, which yaml.v2 used underneath implements, literal 'yes' and 'no' strings without quotation marks will be converted to true/false implicitly.
  123. // - Unlike Unmarshal, all integers, up to 64 bits, are preserved during this round-trip.
  124. // - There are no compatibility guarantees for returned error values.
  125. func YAMLToJSON(y []byte) ([]byte, error) {
  126. return yamlToJSONTarget(y, nil, yaml.Unmarshal)
  127. }
  128. // YAMLToJSONStrict is like YAMLToJSON but enables strict YAML decoding,
  129. // returning an error on any duplicate field names.
  130. func YAMLToJSONStrict(y []byte) ([]byte, error) {
  131. return yamlToJSONTarget(y, nil, yaml.UnmarshalStrict)
  132. }
  133. func yamlToJSONTarget(yamlBytes []byte, jsonTarget *reflect.Value, unmarshalFn func([]byte, interface{}) error) ([]byte, error) {
  134. // Convert the YAML to an object.
  135. var yamlObj interface{}
  136. err := unmarshalFn(yamlBytes, &yamlObj)
  137. if err != nil {
  138. return nil, err
  139. }
  140. // YAML objects are not completely compatible with JSON objects (e.g. you
  141. // can have non-string keys in YAML). So, convert the YAML-compatible object
  142. // to a JSON-compatible object, failing with an error if irrecoverable
  143. // incompatibilties happen along the way.
  144. jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget)
  145. if err != nil {
  146. return nil, err
  147. }
  148. // Convert this object to JSON and return the data.
  149. jsonBytes, err := json.Marshal(jsonObj)
  150. if err != nil {
  151. return nil, err
  152. }
  153. return jsonBytes, nil
  154. }
  155. func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) {
  156. var err error
  157. // Resolve jsonTarget to a concrete value (i.e. not a pointer or an
  158. // interface). We pass decodingNull as false because we're not actually
  159. // decoding into the value, we're just checking if the ultimate target is a
  160. // string.
  161. if jsonTarget != nil {
  162. jsonUnmarshaler, textUnmarshaler, pointerValue := indirect(*jsonTarget, false)
  163. // We have a JSON or Text Umarshaler at this level, so we can't be trying
  164. // to decode into a string.
  165. if jsonUnmarshaler != nil || textUnmarshaler != nil {
  166. jsonTarget = nil
  167. } else {
  168. jsonTarget = &pointerValue
  169. }
  170. }
  171. // If yamlObj is a number or a boolean, check if jsonTarget is a string -
  172. // if so, coerce. Else return normal.
  173. // If yamlObj is a map or array, find the field that each key is
  174. // unmarshaling to, and when you recurse pass the reflect.Value for that
  175. // field back into this function.
  176. switch typedYAMLObj := yamlObj.(type) {
  177. case map[interface{}]interface{}:
  178. // JSON does not support arbitrary keys in a map, so we must convert
  179. // these keys to strings.
  180. //
  181. // From my reading of go-yaml v2 (specifically the resolve function),
  182. // keys can only have the types string, int, int64, float64, binary
  183. // (unsupported), or null (unsupported).
  184. strMap := make(map[string]interface{})
  185. for k, v := range typedYAMLObj {
  186. // Resolve the key to a string first.
  187. var keyString string
  188. switch typedKey := k.(type) {
  189. case string:
  190. keyString = typedKey
  191. case int:
  192. keyString = strconv.Itoa(typedKey)
  193. case int64:
  194. // go-yaml will only return an int64 as a key if the system
  195. // architecture is 32-bit and the key's value is between 32-bit
  196. // and 64-bit. Otherwise the key type will simply be int.
  197. keyString = strconv.FormatInt(typedKey, 10)
  198. case float64:
  199. // Stolen from go-yaml to use the same conversion to string as
  200. // the go-yaml library uses to convert float to string when
  201. // Marshaling.
  202. s := strconv.FormatFloat(typedKey, 'g', -1, 32)
  203. switch s {
  204. case "+Inf":
  205. s = ".inf"
  206. case "-Inf":
  207. s = "-.inf"
  208. case "NaN":
  209. s = ".nan"
  210. }
  211. keyString = s
  212. case bool:
  213. if typedKey {
  214. keyString = "true"
  215. } else {
  216. keyString = "false"
  217. }
  218. default:
  219. return nil, fmt.Errorf("unsupported map key of type: %s, key: %+#v, value: %+#v",
  220. reflect.TypeOf(k), k, v)
  221. }
  222. // jsonTarget should be a struct or a map. If it's a struct, find
  223. // the field it's going to map to and pass its reflect.Value. If
  224. // it's a map, find the element type of the map and pass the
  225. // reflect.Value created from that type. If it's neither, just pass
  226. // nil - JSON conversion will error for us if it's a real issue.
  227. if jsonTarget != nil {
  228. t := *jsonTarget
  229. if t.Kind() == reflect.Struct {
  230. keyBytes := []byte(keyString)
  231. // Find the field that the JSON library would use.
  232. var f *field
  233. fields := cachedTypeFields(t.Type())
  234. for i := range fields {
  235. ff := &fields[i]
  236. if bytes.Equal(ff.nameBytes, keyBytes) {
  237. f = ff
  238. break
  239. }
  240. // Do case-insensitive comparison.
  241. if f == nil && ff.equalFold(ff.nameBytes, keyBytes) {
  242. f = ff
  243. }
  244. }
  245. if f != nil {
  246. // Find the reflect.Value of the most preferential
  247. // struct field.
  248. jtf := t.Field(f.index[0])
  249. strMap[keyString], err = convertToJSONableObject(v, &jtf)
  250. if err != nil {
  251. return nil, err
  252. }
  253. continue
  254. }
  255. } else if t.Kind() == reflect.Map {
  256. // Create a zero value of the map's element type to use as
  257. // the JSON target.
  258. jtv := reflect.Zero(t.Type().Elem())
  259. strMap[keyString], err = convertToJSONableObject(v, &jtv)
  260. if err != nil {
  261. return nil, err
  262. }
  263. continue
  264. }
  265. }
  266. strMap[keyString], err = convertToJSONableObject(v, nil)
  267. if err != nil {
  268. return nil, err
  269. }
  270. }
  271. return strMap, nil
  272. case []interface{}:
  273. // We need to recurse into arrays in case there are any
  274. // map[interface{}]interface{}'s inside and to convert any
  275. // numbers to strings.
  276. // If jsonTarget is a slice (which it really should be), find the
  277. // thing it's going to map to. If it's not a slice, just pass nil
  278. // - JSON conversion will error for us if it's a real issue.
  279. var jsonSliceElemValue *reflect.Value
  280. if jsonTarget != nil {
  281. t := *jsonTarget
  282. if t.Kind() == reflect.Slice {
  283. // By default slices point to nil, but we need a reflect.Value
  284. // pointing to a value of the slice type, so we create one here.
  285. ev := reflect.Indirect(reflect.New(t.Type().Elem()))
  286. jsonSliceElemValue = &ev
  287. }
  288. }
  289. // Make and use a new array.
  290. arr := make([]interface{}, len(typedYAMLObj))
  291. for i, v := range typedYAMLObj {
  292. arr[i], err = convertToJSONableObject(v, jsonSliceElemValue)
  293. if err != nil {
  294. return nil, err
  295. }
  296. }
  297. return arr, nil
  298. default:
  299. // If the target type is a string and the YAML type is a number,
  300. // convert the YAML type to a string.
  301. if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String {
  302. // Based on my reading of go-yaml, it may return int, int64,
  303. // float64, or uint64.
  304. var s string
  305. switch typedVal := typedYAMLObj.(type) {
  306. case int:
  307. s = strconv.FormatInt(int64(typedVal), 10)
  308. case int64:
  309. s = strconv.FormatInt(typedVal, 10)
  310. case float64:
  311. s = strconv.FormatFloat(typedVal, 'g', -1, 32)
  312. case uint64:
  313. s = strconv.FormatUint(typedVal, 10)
  314. case bool:
  315. if typedVal {
  316. s = "true"
  317. } else {
  318. s = "false"
  319. }
  320. }
  321. if len(s) > 0 {
  322. yamlObj = interface{}(s)
  323. }
  324. }
  325. return yamlObj, nil
  326. }
  327. }
  328. // JSONObjectToYAMLObject converts an in-memory JSON object into a YAML in-memory MapSlice,
  329. // without going through a byte representation. A nil or empty map[string]interface{} input is
  330. // converted to an empty map, i.e. yaml.MapSlice(nil).
  331. //
  332. // interface{} slices stay interface{} slices. map[string]interface{} becomes yaml.MapSlice.
  333. //
  334. // int64 and float64 are down casted following the logic of github.com/go-yaml/yaml:
  335. // - float64s are down-casted as far as possible without data-loss to int, int64, uint64.
  336. // - int64s are down-casted to int if possible without data-loss.
  337. //
  338. // Big int/int64/uint64 do not lose precision as in the json-yaml roundtripping case.
  339. //
  340. // string, bool and any other types are unchanged.
  341. func JSONObjectToYAMLObject(j map[string]interface{}) yaml.MapSlice {
  342. if len(j) == 0 {
  343. return nil
  344. }
  345. ret := make(yaml.MapSlice, 0, len(j))
  346. for k, v := range j {
  347. ret = append(ret, yaml.MapItem{Key: k, Value: jsonToYAMLValue(v)})
  348. }
  349. return ret
  350. }
  351. func jsonToYAMLValue(j interface{}) interface{} {
  352. switch j := j.(type) {
  353. case map[string]interface{}:
  354. if j == nil {
  355. return interface{}(nil)
  356. }
  357. return JSONObjectToYAMLObject(j)
  358. case []interface{}:
  359. if j == nil {
  360. return interface{}(nil)
  361. }
  362. ret := make([]interface{}, len(j))
  363. for i := range j {
  364. ret[i] = jsonToYAMLValue(j[i])
  365. }
  366. return ret
  367. case float64:
  368. // replicate the logic in https://github.com/go-yaml/yaml/blob/51d6538a90f86fe93ac480b35f37b2be17fef232/resolve.go#L151
  369. if i64 := int64(j); j == float64(i64) {
  370. if i := int(i64); i64 == int64(i) {
  371. return i
  372. }
  373. return i64
  374. }
  375. if ui64 := uint64(j); j == float64(ui64) {
  376. return ui64
  377. }
  378. return j
  379. case int64:
  380. if i := int(j); j == int64(i) {
  381. return i
  382. }
  383. return j
  384. }
  385. return j
  386. }
  387. // DisallowUnknownFields configures the JSON decoder to error out if unknown
  388. // fields come along, instead of dropping them by default.
  389. func DisallowUnknownFields(d *json.Decoder) *json.Decoder {
  390. d.DisallowUnknownFields()
  391. return d
  392. }