code: func visit(path string, v reflect.Value, t reflect.Type, fn visitor) error {
  	switch v.Kind() {
  	case reflect.String:
  		return fn(path, v)
  	case reflect.Struct:
  		for i := 0; i < v.NumField(); i++ {
  			vf := v.Field(i)
  			tf := t.Field(i)
  			newPath := fmt.Sprintf("%s.%s", path, tf.Name)
  			if err := visit(newPath, vf, tf.Type, fn); err != nil {
  				return err
  			}
  		}
  	case reflect.Slice:
  		for i := 0; i < v.Len(); i++ {
  			vi := v.Index(i)
  			ti := vi.Type()
  			newPath := fmt.Sprintf("%s[%d]", path, i)
  			if err := visit(newPath, vi, ti, fn); err != nil {
  				return err
  			}
  		}
  	case reflect.Map:
  		for _, key := range v.MapKeys() {
  			value := v.MapIndex(key)
  
  			newValue := reflect.New(value.Type()).Elem()
  			newValue.SetString(value.String())
  
  			if err := visit(fmt.Sprintf("%s[%s]", path, key.String()), newValue, newValue.Type(), fn); err != nil {
  				return err
  			}
  
  			// overwrite the entry in case it was modified by the callback
  			v.SetMapIndex(key, newValue)
  		}
  	}
  	return nil
  }
docs: // visit calls the visitor function for each string it finds, and will descend
  // recursively into structures and slices. If any visitor returns an error then
  // the search will stop and that error will be returned.