code.cestus.io/tools/fabricator@v0.4.3/pkg/ff/json.go (about) 1 package ff 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "strconv" 8 ) 9 10 // JSONParser is a parser for config files in JSON format. Input should be 11 // an object. The object's keys are treated as flag names, and the object's 12 // values as flag values. If the value is an array, the flag will be set 13 // multiple times. 14 func JSONParser(r io.Reader, set func(name, value string) error) error { 15 var m map[string]interface{} 16 d := json.NewDecoder(r) 17 d.UseNumber() // must set UseNumber for stringifyValue to work 18 if err := d.Decode(&m); err != nil { 19 return JSONParseError{Inner: err} 20 } 21 for key, val := range m { 22 values, err := stringifySlice(val) 23 if err != nil { 24 return JSONParseError{Inner: err} 25 } 26 for _, value := range values { 27 if err := set(key, value); err != nil { 28 return err 29 } 30 } 31 } 32 return nil 33 } 34 35 func stringifySlice(val interface{}) ([]string, error) { 36 if vals, ok := val.([]interface{}); ok { 37 ss := make([]string, len(vals)) 38 for i := range vals { 39 s, err := stringifyValue(vals[i]) 40 if err != nil { 41 return nil, err 42 } 43 ss[i] = s 44 } 45 return ss, nil 46 } 47 s, err := stringifyValue(val) 48 if err != nil { 49 return nil, err 50 } 51 return []string{s}, nil 52 } 53 54 func stringifyValue(val interface{}) (string, error) { 55 switch v := val.(type) { 56 case string: 57 return v, nil 58 case json.Number: 59 return v.String(), nil 60 case bool: 61 return strconv.FormatBool(v), nil 62 default: 63 return "", StringConversionError{Value: val} 64 } 65 } 66 67 // JSONParseError wraps all errors originating from the JSONParser. 68 type JSONParseError struct { 69 Inner error 70 } 71 72 // Error implenents the error interface. 73 func (e JSONParseError) Error() string { 74 return fmt.Sprintf("error parsing JSON config: %v", e.Inner) 75 } 76 77 // Unwrap implements the errors.Wrapper interface, allowing errors.Is and 78 // errors.As to work with JSONParseErrors. 79 func (e JSONParseError) Unwrap() error { 80 return e.Inner 81 } 82 83 // StringConversionError is returned when a value in a config file 84 // can't be converted to a string, to be provided to a flag. 85 type StringConversionError struct { 86 Value interface{} 87 } 88 89 // Error implements the error interface. 90 func (e StringConversionError) Error() string { 91 return fmt.Sprintf("couldn't convert %q (type %T) to string", e.Value, e.Value) 92 }