We can try to use reflection to create a package that reads property files.
We can use reflection to create a package that reads property files:
- The first thing we should do is define an interface that avoids using reflection:
type Unmarshaller interface {
UnmarshalProp([]byte) error
}
- Then, we can define a decoder structure that will feed on an io.Reader instance, using a line scanner to read the individual properties:
type Decoder struct {
scanner *bufio.Scanner
}
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{scanner: bufio.NewScanner(r)}
}
- The decoder will also be used by the Unmarshal method:
func Unmarshal(data []byte, v interface{}) error {
return NewDecoder(bytes.NewReader(data)).Decode(v)
}
- We can reduce the number of uses of reflection that we will do by building a cache of field names and indices. This will be helpful because the value of a field in reflection can only be accessed by an index, and not by a name:
var cache = make(map[reflect.Type]map[string]int)
func findIndex(t reflect.Type, k string) (int, bool) {
if v, ok := cache[t]; ok {
n, ok := v[k]
return n, ok
}
m := make(map[string]int)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if s := f.Name[:1]; strings.ToLower(s) == s {
continue
}
name := strings.ToLower(f.Name)
if tag := f.Tag.Get("prop"); tag != "" {
name = tag
}
m[name] = i
}
cache[t] = m
return findIndex(t, k)
}
- The next step is defining the Decode method. This will receive a pointer to a structure and then proceed to process lines from the scanner and populate the structure fields:
func (d *Decoder) Decode(v interface{}) error {
val := reflect.ValueOf(v)
t := val.Type()
if t.Kind() != reflect.Ptr && t.Elem().Kind() != reflect.Struct {
return fmt.Errorf("%v not a struct pointer", t)
}
val = val.Elem()
t = t.Elem()
line := 0
for d.scanner.Scan() {
line++
b := d.scanner.Bytes()
if len(b) == 0 || b[0] == '#' {
continue
}
parts := bytes.SplitN(b, []byte{':'}, 2)
if len(parts) != 2 {
return decodeError{line: line, err: errNoSep}
}
index, ok := findIndex(t, string(parts[0]))
if !ok {
continue
}
value := bytes.TrimSpace(parts[1])
if err := d.decodeValue(val.Field(index), value); err != nil {
return decodeError{line: line, err: err}
}
}
return d.scanner.Err()
}
The most important part of the work will be done by the private decodeValue method. The first thing will be verifying that the Unmarshaller interface is satisfied, and, if it is, using it. Otherwise, the method is going to use reflection to decode the value received correctly. For each type, it will use a different Set method from reflection.Value, and it will return an error if it encounters an unknown type:
func (d *Decoder) decodeValue(v reflect.Value, value []byte) error {
if v, ok := v.Addr().Interface().(Unmarshaller); ok {
return v.UnmarshalProp(value)
}
switch valStr := string(value); v.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, err := strconv.ParseInt(valStr, 10, 64)
if err != nil {
return err
}
v.SetInt(i)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
i, err := strconv.ParseUint(valStr, 10, 64)
if err != nil {
return err
}
v.SetUint(i)
case reflect.Float32, reflect.Float64:
i, err := strconv.ParseFloat(valStr, 64)
if err != nil {
return err
}
v.SetFloat(i)
case reflect.String:
v.SetString(valStr)
case reflect.Bool:
switch value := valStr; value {
case "true":
v.SetBool(true)
case "false":
v.SetBool(false)
default:
return fmt.Errorf("invalid bool: %s", value)
}
default:
return fmt.Errorf("invalid type: %s", v.Type())
}
return nil
}