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