github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/formatter/reflect.go (about) 1 // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: 2 //go:build go1.19 3 4 package formatter 5 6 import ( 7 "encoding/json" 8 "reflect" 9 "unicode" 10 11 "github.com/pkg/errors" 12 ) 13 14 // MarshalJSON marshals x into json 15 // It differs a bit from encoding/json MarshalJSON function for formatter 16 func MarshalJSON(x any) ([]byte, error) { 17 m, err := marshalMap(x) 18 if err != nil { 19 return nil, err 20 } 21 return json.Marshal(m) 22 } 23 24 // marshalMap marshals x to map[string]any 25 func marshalMap(x any) (map[string]any, error) { 26 val := reflect.ValueOf(x) 27 if val.Kind() != reflect.Ptr { 28 return nil, errors.Errorf("expected a pointer to a struct, got %v", val.Kind()) 29 } 30 if val.IsNil() { 31 return nil, errors.Errorf("expected a pointer to a struct, got nil pointer") 32 } 33 valElem := val.Elem() 34 if valElem.Kind() != reflect.Struct { 35 return nil, errors.Errorf("expected a pointer to a struct, got a pointer to %v", valElem.Kind()) 36 } 37 typ := val.Type() 38 m := make(map[string]any) 39 for i := 0; i < val.NumMethod(); i++ { 40 k, v, err := marshalForMethod(typ.Method(i), val.Method(i)) 41 if err != nil { 42 return nil, err 43 } 44 if k != "" { 45 m[k] = v 46 } 47 } 48 return m, nil 49 } 50 51 var unmarshallableNames = map[string]struct{}{"FullHeader": {}} 52 53 // marshalForMethod returns the map key and the map value for marshalling the method. 54 // It returns ("", nil, nil) for valid but non-marshallable parameter. (e.g. "unexportedFunc()") 55 func marshalForMethod(typ reflect.Method, val reflect.Value) (string, any, error) { 56 if val.Kind() != reflect.Func { 57 return "", nil, errors.Errorf("expected func, got %v", val.Kind()) 58 } 59 name, numIn, numOut := typ.Name, val.Type().NumIn(), val.Type().NumOut() 60 _, blackListed := unmarshallableNames[name] 61 // FIXME: In text/template, (numOut == 2) is marshallable, 62 // if the type of the second param is error. 63 marshallable := unicode.IsUpper(rune(name[0])) && !blackListed && 64 numIn == 0 && numOut == 1 65 if !marshallable { 66 return "", nil, nil 67 } 68 result := val.Call(make([]reflect.Value, numIn)) 69 intf := result[0].Interface() 70 return name, intf, nil 71 }