collections.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /*
  2. Copyright 2025 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 json
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "io"
  18. "maps"
  19. "slices"
  20. "sort"
  21. "k8s.io/apimachinery/pkg/api/meta"
  22. "k8s.io/apimachinery/pkg/conversion"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  25. "k8s.io/apimachinery/pkg/runtime"
  26. )
  27. func streamEncodeCollections(obj runtime.Object, w io.Writer) (bool, error) {
  28. list, ok := obj.(*unstructured.UnstructuredList)
  29. if ok {
  30. return true, streamingEncodeUnstructuredList(w, list)
  31. }
  32. if _, ok := obj.(json.Marshaler); ok {
  33. return false, nil
  34. }
  35. typeMeta, listMeta, items, err := getListMeta(obj)
  36. if err == nil {
  37. return true, streamingEncodeList(w, typeMeta, listMeta, items)
  38. }
  39. return false, nil
  40. }
  41. // getListMeta implements list extraction logic for json stream serialization.
  42. //
  43. // Reason for a custom logic instead of reusing accessors from meta package:
  44. // * Validate json tags to prevent incompatibility with json standard package.
  45. // * ListMetaAccessor doesn't distinguish empty from nil value.
  46. // * TypeAccessort reparsing "apiVersion" and serializing it with "{group}/{version}"
  47. func getListMeta(list runtime.Object) (metav1.TypeMeta, metav1.ListMeta, []runtime.Object, error) {
  48. listValue, err := conversion.EnforcePtr(list)
  49. if err != nil {
  50. return metav1.TypeMeta{}, metav1.ListMeta{}, nil, err
  51. }
  52. listType := listValue.Type()
  53. if listType.NumField() != 3 {
  54. return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected ListType to have 3 fields")
  55. }
  56. // TypeMeta
  57. typeMeta, ok := listValue.Field(0).Interface().(metav1.TypeMeta)
  58. if !ok {
  59. return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected TypeMeta field to have TypeMeta type")
  60. }
  61. if listType.Field(0).Tag.Get("json") != ",inline" {
  62. return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected TypeMeta json field tag to be ",inline"`)
  63. }
  64. // ListMeta
  65. listMeta, ok := listValue.Field(1).Interface().(metav1.ListMeta)
  66. if !ok {
  67. return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected ListMeta field to have ListMeta type")
  68. }
  69. if listType.Field(1).Tag.Get("json") != "metadata,omitempty" {
  70. return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected ListMeta json field tag to be "metadata,omitempty"`)
  71. }
  72. // Items
  73. items, err := meta.ExtractList(list)
  74. if err != nil {
  75. return metav1.TypeMeta{}, metav1.ListMeta{}, nil, err
  76. }
  77. if listType.Field(2).Tag.Get("json") != "items" {
  78. return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected Items json field tag to be "items"`)
  79. }
  80. return typeMeta, listMeta, items, nil
  81. }
  82. func streamingEncodeList(w io.Writer, typeMeta metav1.TypeMeta, listMeta metav1.ListMeta, items []runtime.Object) error {
  83. // Start
  84. if _, err := w.Write([]byte(`{`)); err != nil {
  85. return err
  86. }
  87. // TypeMeta
  88. if typeMeta.Kind != "" {
  89. if err := encodeKeyValuePair(w, "kind", typeMeta.Kind, []byte(",")); err != nil {
  90. return err
  91. }
  92. }
  93. if typeMeta.APIVersion != "" {
  94. if err := encodeKeyValuePair(w, "apiVersion", typeMeta.APIVersion, []byte(",")); err != nil {
  95. return err
  96. }
  97. }
  98. // ListMeta
  99. if err := encodeKeyValuePair(w, "metadata", listMeta, []byte(",")); err != nil {
  100. return err
  101. }
  102. // Items
  103. if err := encodeItemsObjectSlice(w, items); err != nil {
  104. return err
  105. }
  106. // End
  107. _, err := w.Write([]byte("}\n"))
  108. return err
  109. }
  110. func encodeItemsObjectSlice(w io.Writer, items []runtime.Object) (err error) {
  111. if items == nil {
  112. err := encodeKeyValuePair(w, "items", nil, nil)
  113. return err
  114. }
  115. _, err = w.Write([]byte(`"items":[`))
  116. if err != nil {
  117. return err
  118. }
  119. suffix := []byte(",")
  120. for i, item := range items {
  121. if i == len(items)-1 {
  122. suffix = nil
  123. }
  124. err := encodeValue(w, item, suffix)
  125. if err != nil {
  126. return err
  127. }
  128. }
  129. _, err = w.Write([]byte("]"))
  130. if err != nil {
  131. return err
  132. }
  133. return err
  134. }
  135. func streamingEncodeUnstructuredList(w io.Writer, list *unstructured.UnstructuredList) error {
  136. _, err := w.Write([]byte(`{`))
  137. if err != nil {
  138. return err
  139. }
  140. keys := slices.Collect(maps.Keys(list.Object))
  141. if _, exists := list.Object["items"]; !exists {
  142. keys = append(keys, "items")
  143. }
  144. sort.Strings(keys)
  145. suffix := []byte(",")
  146. for i, key := range keys {
  147. if i == len(keys)-1 {
  148. suffix = nil
  149. }
  150. if key == "items" {
  151. err = encodeItemsUnstructuredSlice(w, list.Items, suffix)
  152. } else {
  153. err = encodeKeyValuePair(w, key, list.Object[key], suffix)
  154. }
  155. if err != nil {
  156. return err
  157. }
  158. }
  159. _, err = w.Write([]byte("}\n"))
  160. return err
  161. }
  162. func encodeItemsUnstructuredSlice(w io.Writer, items []unstructured.Unstructured, suffix []byte) (err error) {
  163. _, err = w.Write([]byte(`"items":[`))
  164. if err != nil {
  165. return err
  166. }
  167. comma := []byte(",")
  168. for i, item := range items {
  169. if i == len(items)-1 {
  170. comma = nil
  171. }
  172. err := encodeValue(w, item.Object, comma)
  173. if err != nil {
  174. return err
  175. }
  176. }
  177. _, err = w.Write([]byte("]"))
  178. if err != nil {
  179. return err
  180. }
  181. if len(suffix) > 0 {
  182. _, err = w.Write(suffix)
  183. }
  184. return err
  185. }
  186. func encodeKeyValuePair(w io.Writer, key string, value any, suffix []byte) (err error) {
  187. err = encodeValue(w, key, []byte(":"))
  188. if err != nil {
  189. return err
  190. }
  191. err = encodeValue(w, value, suffix)
  192. if err != nil {
  193. return err
  194. }
  195. return err
  196. }
  197. func encodeValue(w io.Writer, value any, suffix []byte) error {
  198. data, err := json.Marshal(value)
  199. if err != nil {
  200. return err
  201. }
  202. _, err = w.Write(data)
  203. if err != nil {
  204. return err
  205. }
  206. if len(suffix) > 0 {
  207. _, err = w.Write(suffix)
  208. }
  209. return err
  210. }