Skip to content

Instantly share code, notes, and snippets.

@icio
Forked from anonymous/run.sh
Created February 8, 2017 19:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save icio/69fe0f850ed1c4c1ae323546f78bdfcd to your computer and use it in GitHub Desktop.
Save icio/69fe0f850ed1c4c1ae323546f78bdfcd to your computer and use it in GitHub Desktop.
Go: Reflecting valid values
$ go run valid.go
main.Thing{Age:sql.NullInt64{Int64:99, Valid:true}, Lender:(*sql.NullString)(nil), Blue:sql.NullBool{Bool:false, Valid:false}}:
- Age: 99
main.Thing{Age:sql.NullInt64{Int64:1, Valid:true}, Lender:(*sql.NullString)(0xc82008a000), Blue:sql.NullBool{Bool:false, Valid:false}}:
- Age: 1
&main.Thing{Age:sql.NullInt64{Int64:0, Valid:false}, Lender:(*sql.NullString)(0xc82008a020), Blue:sql.NullBool{Bool:false, Valid:false}}:
- Lender: "Friend"
&main.Thing{Age:sql.NullInt64{Int64:0, Valid:false}, Lender:(*sql.NullString)(nil), Blue:sql.NullBool{Bool:true, Valid:true}}:
- Blue: true
&main.Thing{Age:sql.NullInt64{Int64:44, Valid:true}, Lender:(*sql.NullString)(0xc82008a080), Blue:sql.NullBool{Bool:false, Valid:true}}:
- Age: 44
- Lender: "Derp"
- Blue: false
"woops":
- Unhandled type: string
package main
import (
"fmt"
"reflect"
"strings"
// github.com/unravelin/null wraps database/sql's nulls, which we'll use
// directly for illustrative purposes.
null "database/sql"
)
func main() {
things := []interface{}{
Thing{Age: Int(99)}, // Something old
Thing{Age: Int(1), Lender: &null.NullString{}}, // Something new
&Thing{Lender: String("Friend")}, // Something borrowed
&Thing{Blue: Bool(true)}, // Something blue
&Thing{Age: Int(44), Lender: String("Derp"), Blue: Bool(false)}, // Something slightly different
"woops",
}
for _, thing := range things {
fmt.Printf(
"%#v:\n- %s\n",
thing,
strings.Join(ValidFields(thing), "\n- "),
)
}
}
// ValidFields returns a description of "Valid" fields on an object. Valid
// fields are determined by the value being a struct with field Valid=true.
// Usually we'd want to return an error to describe when something other than
// a struct we can work with is given, but here we just return an error string.
func ValidFields(thing interface{}) []string {
// Reflect on the thing given to us. This will allow us to begin inspecting
// ask about thing's type and the fields on it.
obj := reflect.ValueOf(thing)
// If thing is a pointer to a value, we can't ask for obj.NumField() yet.
// Instead we should be asking for obj.Elem().NumField() - effectively
// dereferencing the pointer.
if obj.Kind() == reflect.Ptr {
obj = obj.Elem()
}
// If we weren't given a struct we are unable to loop over its fields.
if obj.Kind() != reflect.Struct {
return []string{fmt.Sprintf("Unhandled type: %T", thing)}
}
// At this point we know we obj reflects a Struct and can loop over its
// fields to figure out whether it's valid.
validFields := make([]string, 0)
for f := 0; f < obj.NumField(); f++ {
fieldName := obj.Type().Field(f).Name
field := obj.Field(f)
// We now have field reflecting one of the fields of our input struct.
// We want to determine whether it is valid. There are two options:
// 1) we can examine field.FieldByName("Valid") to confirm it is true,
// and then figure out which other fields are set on the struct such
// that can describe what the actual value is meant to be; or 2) we can
// attempt to identify the exact type of the field and check each type
// manually. Lets try the second:
// The field that we're looking at might be a NullString or a
// *NullString, so we're going to ensure we always end up with the
// value form (involves some copying, unfortunately):
if field.Kind() == reflect.Ptr {
field = field.Elem()
}
if !field.IsValid() || !field.CanInterface() {
// There are cases where we can't procede, such as dealing with nils.
continue
}
valInterface := field.Interface()
switch val := valInterface.(type) {
case null.NullBool:
// If val is a *null.NullBool then val will take the appropriate
// type and we can use it as such here:
if val.Valid {
validFields = append(validFields, fmt.Sprintf("%s: %v", fieldName, val.Bool))
}
case null.NullString:
if val.Valid {
validFields = append(validFields, fmt.Sprintf("%s: %q", fieldName, val.String))
}
case null.NullInt64:
if val.Valid {
validFields = append(validFields, fmt.Sprintf("%s: %v", fieldName, val.Int64))
}
}
}
return validFields
}
type Thing struct {
Age null.NullInt64
Lender *null.NullString
Blue null.NullBool
}
func String(str string) *null.NullString {
return &null.NullString{str, true}
}
func Bool(b bool) null.NullBool {
return null.NullBool{b, true}
}
func Int(i int64) null.NullInt64 {
return null.NullInt64{i, true}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment