go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/api/internal/gensupport/json.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package gensupport 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "reflect" 11 "strings" 12 ) 13 14 // MarshalJSON returns a JSON encoding of schema containing only selected fields. 15 // A field is selected if any of the following is true: 16 // - it has a non-empty value 17 // - its field name is present in forceSendFields and it is not a nil pointer or nil interface 18 // - its field name is present in nullFields. 19 // 20 // The JSON key for each selected field is taken from the field's json: struct tag. 21 func MarshalJSON(schema interface{}, forceSendFields, nullFields []string) ([]byte, error) { 22 if len(forceSendFields) == 0 && len(nullFields) == 0 { 23 return json.Marshal(schema) 24 } 25 26 mustInclude := make(map[string]bool) 27 for _, f := range forceSendFields { 28 mustInclude[f] = true 29 } 30 useNull := make(map[string]bool) 31 useNullMaps := make(map[string]map[string]bool) 32 for _, nf := range nullFields { 33 parts := strings.SplitN(nf, ".", 2) 34 field := parts[0] 35 if len(parts) == 1 { 36 useNull[field] = true 37 } else { 38 if useNullMaps[field] == nil { 39 useNullMaps[field] = map[string]bool{} 40 } 41 useNullMaps[field][parts[1]] = true 42 } 43 } 44 45 dataMap, err := schemaToMap(schema, mustInclude, useNull, useNullMaps) 46 if err != nil { 47 return nil, err 48 } 49 return json.Marshal(dataMap) 50 } 51 52 func schemaToMap(schema interface{}, mustInclude, useNull map[string]bool, useNullMaps map[string]map[string]bool) (map[string]interface{}, error) { 53 m := make(map[string]interface{}) 54 s := reflect.ValueOf(schema) 55 st := s.Type() 56 57 for i := 0; i < s.NumField(); i++ { 58 jsonTag := st.Field(i).Tag.Get("json") 59 if jsonTag == "" { 60 continue 61 } 62 tag, err := parseJSONTag(jsonTag) 63 if err != nil { 64 return nil, err 65 } 66 if tag.ignore { 67 continue 68 } 69 70 v := s.Field(i) 71 f := st.Field(i) 72 73 if useNull[f.Name] { 74 if !isEmptyValue(v) { 75 return nil, fmt.Errorf("field %q in NullFields has non-empty value", f.Name) 76 } 77 m[tag.apiName] = nil 78 continue 79 } 80 81 if !includeField(v, f, mustInclude) { 82 continue 83 } 84 85 // If map fields are explicitly set to null, use a map[string]interface{}. 86 if f.Type.Kind() == reflect.Map && useNullMaps[f.Name] != nil { 87 ms, ok := v.Interface().(map[string]string) 88 if !ok { 89 mi, err := initMapSlow(v, f.Name, useNullMaps) 90 if err != nil { 91 return nil, err 92 } 93 m[tag.apiName] = mi 94 continue 95 } 96 mi := map[string]interface{}{} 97 for k, v := range ms { 98 mi[k] = v 99 } 100 for k := range useNullMaps[f.Name] { 101 mi[k] = nil 102 } 103 m[tag.apiName] = mi 104 continue 105 } 106 107 // nil maps are treated as empty maps. 108 if f.Type.Kind() == reflect.Map && v.IsNil() { 109 m[tag.apiName] = map[string]string{} 110 continue 111 } 112 113 // nil slices are treated as empty slices. 114 if f.Type.Kind() == reflect.Slice && v.IsNil() { 115 m[tag.apiName] = []bool{} 116 continue 117 } 118 119 if tag.stringFormat { 120 m[tag.apiName] = formatAsString(v, f.Type.Kind()) 121 } else { 122 m[tag.apiName] = v.Interface() 123 } 124 } 125 return m, nil 126 } 127 128 // initMapSlow uses reflection to build up a map object. This is slower than 129 // the default behavior so it should be used only as a fallback. 130 func initMapSlow(rv reflect.Value, fieldName string, useNullMaps map[string]map[string]bool) (map[string]interface{}, error) { 131 mi := map[string]interface{}{} 132 iter := rv.MapRange() 133 for iter.Next() { 134 k, ok := iter.Key().Interface().(string) 135 if !ok { 136 return nil, fmt.Errorf("field %q has keys in NullFields but is not a map[string]any", fieldName) 137 } 138 v := iter.Value().Interface() 139 mi[k] = v 140 } 141 for k := range useNullMaps[fieldName] { 142 mi[k] = nil 143 } 144 return mi, nil 145 } 146 147 // formatAsString returns a string representation of v, dereferencing it first if possible. 148 func formatAsString(v reflect.Value, kind reflect.Kind) string { 149 if kind == reflect.Ptr && !v.IsNil() { 150 v = v.Elem() 151 } 152 153 return fmt.Sprintf("%v", v.Interface()) 154 } 155 156 // jsonTag represents a restricted version of the struct tag format used by encoding/json. 157 // It is used to describe the JSON encoding of fields in a Schema struct. 158 type jsonTag struct { 159 apiName string 160 stringFormat bool 161 ignore bool 162 } 163 164 // parseJSONTag parses a restricted version of the struct tag format used by encoding/json. 165 // The format of the tag must match that generated by the Schema.writeSchemaStruct method 166 // in the api generator. 167 func parseJSONTag(val string) (jsonTag, error) { 168 if val == "-" { 169 return jsonTag{ignore: true}, nil 170 } 171 172 var tag jsonTag 173 174 i := strings.Index(val, ",") 175 if i == -1 || val[:i] == "" { 176 return tag, fmt.Errorf("malformed json tag: %s", val) 177 } 178 179 tag = jsonTag{ 180 apiName: val[:i], 181 } 182 183 switch val[i+1:] { 184 case "omitempty": 185 case "omitempty,string": 186 tag.stringFormat = true 187 default: 188 return tag, fmt.Errorf("malformed json tag: %s", val) 189 } 190 191 return tag, nil 192 } 193 194 // Reports whether the struct field "f" with value "v" should be included in JSON output. 195 func includeField(v reflect.Value, f reflect.StructField, mustInclude map[string]bool) bool { 196 // The regular JSON encoding of a nil pointer is "null", which means "delete this field". 197 // Therefore, we could enable field deletion by honoring pointer fields' presence in the mustInclude set. 198 // However, many fields are not pointers, so there would be no way to delete these fields. 199 // Rather than partially supporting field deletion, we ignore mustInclude for nil pointer fields. 200 // Deletion will be handled by a separate mechanism. 201 if f.Type.Kind() == reflect.Ptr && v.IsNil() { 202 return false 203 } 204 205 // The "any" type is represented as an interface{}. If this interface 206 // is nil, there is no reasonable representation to send. We ignore 207 // these fields, for the same reasons as given above for pointers. 208 if f.Type.Kind() == reflect.Interface && v.IsNil() { 209 return false 210 } 211 212 return mustInclude[f.Name] || !isEmptyValue(v) 213 } 214 215 // isEmptyValue reports whether v is the empty value for its type. This 216 // implementation is based on that of the encoding/json package, but its 217 // correctness does not depend on it being identical. What's important is that 218 // this function return false in situations where v should not be sent as part 219 // of a PATCH operation. 220 func isEmptyValue(v reflect.Value) bool { 221 switch v.Kind() { 222 case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 223 return v.Len() == 0 224 case reflect.Bool: 225 return !v.Bool() 226 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 227 return v.Int() == 0 228 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 229 return v.Uint() == 0 230 case reflect.Float32, reflect.Float64: 231 return v.Float() == 0 232 case reflect.Interface, reflect.Ptr: 233 return v.IsNil() 234 } 235 return false 236 }