Created
February 8, 2017 19:22
-
-
Save anonymous/0334f9220597dd04dea86bfe11536d01 to your computer and use it in GitHub Desktop.
Go: Reflecting valid values
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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