unstructured.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. Copyright 2018 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 value
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "gopkg.in/yaml.v2"
  18. )
  19. // FromYAML is a helper function for reading a YAML document; it attempts to
  20. // preserve order of keys within maps/structs. This is as a convenience to
  21. // humans keeping YAML documents, not because there is a behavior difference.
  22. //
  23. // Known bug: objects with top-level arrays don't parse correctly.
  24. func FromYAML(input []byte) (Value, error) {
  25. var decoded interface{}
  26. if len(input) == 4 && string(input) == "null" {
  27. // Special case since the yaml package doesn't accurately
  28. // preserve this.
  29. return Value{Null: true}, nil
  30. }
  31. // This attempts to enable order sensitivity; note the yaml package is
  32. // broken for documents that have root-level arrays, hence the two-step
  33. // approach. TODO: This is a horrific hack. Is it worth it?
  34. var ms yaml.MapSlice
  35. if err := yaml.Unmarshal(input, &ms); err == nil {
  36. decoded = ms
  37. } else if err := yaml.Unmarshal(input, &decoded); err != nil {
  38. return Value{}, err
  39. }
  40. v, err := FromUnstructured(decoded)
  41. if err != nil {
  42. return Value{}, fmt.Errorf("failed to interpret (%v):\n%s", err, input)
  43. }
  44. return v, nil
  45. }
  46. // FromJSON is a helper function for reading a JSON document
  47. func FromJSON(input []byte) (Value, error) {
  48. var decoded interface{}
  49. if err := json.Unmarshal(input, &decoded); err != nil {
  50. return Value{}, err
  51. }
  52. v, err := FromUnstructured(decoded)
  53. if err != nil {
  54. return Value{}, fmt.Errorf("failed to interpret (%v):\n%s", err, input)
  55. }
  56. return v, nil
  57. }
  58. // FromUnstructured will convert a go interface to a Value.
  59. // It's most commonly expected to be used with map[string]interface{} as the
  60. // input. `in` must not have any structures with cycles in them.
  61. // yaml.MapSlice may be used for order-preservation.
  62. func FromUnstructured(in interface{}) (Value, error) {
  63. if in == nil {
  64. return Value{Null: true}, nil
  65. }
  66. switch t := in.(type) {
  67. case map[interface{}]interface{}:
  68. m := Map{}
  69. for rawKey, rawVal := range t {
  70. k, ok := rawKey.(string)
  71. if !ok {
  72. return Value{}, fmt.Errorf("key %#v: not a string", k)
  73. }
  74. v, err := FromUnstructured(rawVal)
  75. if err != nil {
  76. return Value{}, fmt.Errorf("key %v: %v", k, err)
  77. }
  78. m.Set(k, v)
  79. }
  80. return Value{MapValue: &m}, nil
  81. case map[string]interface{}:
  82. m := Map{}
  83. for k, rawVal := range t {
  84. v, err := FromUnstructured(rawVal)
  85. if err != nil {
  86. return Value{}, fmt.Errorf("key %v: %v", k, err)
  87. }
  88. m.Set(k, v)
  89. }
  90. return Value{MapValue: &m}, nil
  91. case yaml.MapSlice:
  92. m := Map{}
  93. for _, item := range t {
  94. k, ok := item.Key.(string)
  95. if !ok {
  96. return Value{}, fmt.Errorf("key %#v is not a string", item.Key)
  97. }
  98. v, err := FromUnstructured(item.Value)
  99. if err != nil {
  100. return Value{}, fmt.Errorf("key %v: %v", k, err)
  101. }
  102. m.Set(k, v)
  103. }
  104. return Value{MapValue: &m}, nil
  105. case []interface{}:
  106. l := List{}
  107. for i, rawVal := range t {
  108. v, err := FromUnstructured(rawVal)
  109. if err != nil {
  110. return Value{}, fmt.Errorf("index %v: %v", i, err)
  111. }
  112. l.Items = append(l.Items, v)
  113. }
  114. return Value{ListValue: &l}, nil
  115. case int:
  116. n := Int(t)
  117. return Value{IntValue: &n}, nil
  118. case int8:
  119. n := Int(t)
  120. return Value{IntValue: &n}, nil
  121. case int16:
  122. n := Int(t)
  123. return Value{IntValue: &n}, nil
  124. case int32:
  125. n := Int(t)
  126. return Value{IntValue: &n}, nil
  127. case int64:
  128. n := Int(t)
  129. return Value{IntValue: &n}, nil
  130. case uint:
  131. n := Int(t)
  132. return Value{IntValue: &n}, nil
  133. case uint8:
  134. n := Int(t)
  135. return Value{IntValue: &n}, nil
  136. case uint16:
  137. n := Int(t)
  138. return Value{IntValue: &n}, nil
  139. case uint32:
  140. n := Int(t)
  141. return Value{IntValue: &n}, nil
  142. case float32:
  143. f := Float(t)
  144. return Value{FloatValue: &f}, nil
  145. case float64:
  146. f := Float(t)
  147. return Value{FloatValue: &f}, nil
  148. case string:
  149. return StringValue(t), nil
  150. case bool:
  151. return BooleanValue(t), nil
  152. default:
  153. return Value{}, fmt.Errorf("type unimplemented: %t", in)
  154. }
  155. }
  156. // ToYAML is a helper function for producing a YAML document; it attempts to
  157. // preserve order of keys within maps/structs. This is as a convenience to
  158. // humans keeping YAML documents, not because there is a behavior difference.
  159. func (v *Value) ToYAML() ([]byte, error) {
  160. return yaml.Marshal(v.ToUnstructured(true))
  161. }
  162. // ToJSON is a helper function for producing a JSon document.
  163. func (v *Value) ToJSON() ([]byte, error) {
  164. return json.Marshal(v.ToUnstructured(false))
  165. }
  166. // ToUnstructured will convert the Value into a go-typed object.
  167. // If preserveOrder is true, then maps will be converted to the yaml.MapSlice
  168. // type. Otherwise, map[string]interface{} must be used-- this destroys
  169. // ordering information and is not recommended if the result of this will be
  170. // serialized. Other types:
  171. // * list -> []interface{}
  172. // * others -> corresponding go type, wrapped in an interface{}
  173. //
  174. // Of note, floats and ints will always come out as float64 and int64,
  175. // respectively.
  176. func (v *Value) ToUnstructured(preserveOrder bool) interface{} {
  177. switch {
  178. case v.FloatValue != nil:
  179. f := float64(*v.FloatValue)
  180. return f
  181. case v.IntValue != nil:
  182. i := int64(*v.IntValue)
  183. return i
  184. case v.StringValue != nil:
  185. return string(*v.StringValue)
  186. case v.BooleanValue != nil:
  187. return bool(*v.BooleanValue)
  188. case v.ListValue != nil:
  189. out := []interface{}{}
  190. for _, item := range v.ListValue.Items {
  191. out = append(out, item.ToUnstructured(preserveOrder))
  192. }
  193. return out
  194. case v.MapValue != nil:
  195. m := v.MapValue
  196. if preserveOrder {
  197. ms := make(yaml.MapSlice, len(m.Items))
  198. for i := range m.Items {
  199. ms[i] = yaml.MapItem{
  200. Key: m.Items[i].Name,
  201. Value: m.Items[i].Value.ToUnstructured(preserveOrder),
  202. }
  203. }
  204. return ms
  205. }
  206. // This case is unavoidably lossy.
  207. out := map[string]interface{}{}
  208. for i := range m.Items {
  209. out[m.Items[i].Name] = m.Items[i].Value.ToUnstructured(preserveOrder)
  210. }
  211. return out
  212. default:
  213. fallthrough
  214. case v.Null == true:
  215. return nil
  216. }
  217. }