Structs
suggest changeList fields of a struct
Using reflection we can list all fields of a struct.
type S struct {
FirstName string `my_tag:"first-name"`
lastName string
Age int `json:"age",xml:"AgeXml`
}
func describeStructSimple(rv reflect.Value) {
structType := rv.Type()
for i := 0; i < rv.NumField(); i++ {
v := rv.Field(i)
structField := structType.Field(i)
name := structField.Name
typ := structField.Type
tag := structField.Tag
jsonTag := tag.Get("json")
isExported := structField.PkgPath == ""
if isExported {
fmt.Printf("name: '%s',\ttype: '%s', value: %v,\ttag: '%s',\tjson tag: '%s'\n", name, typ, v.Interface(), tag, jsonTag)
} else {
fmt.Printf("name: '%s',\ttype: '%s',\tvalue: not accessible\n", name, v.Type().Name())
}
}
}
func main() {
s := S{
FirstName: "John",
lastName: "Doe",
Age: 27,
}
describeStructSimple(reflect.ValueOf(s))
}
name: 'FirstName', type: 'string', value: John, tag: 'my_tag:"first-name"', json tag: ''
name: 'lastName', type: 'string', value: not accessible
name: 'Age', type: 'int', value: 27, tag: 'json:"age",xml:"AgeXml', json tag: 'age'
Using reflection we can only access values (v.Interface{}
) of exported fields.
Exported fields are fields with names starting with upper case (FirstName
and Age
are exported, lastName
is not).
Field is exported if reflect.StructField.PkgPath == ""
.
List fields of a struct recursively
Inspecting a struct is inherently recursive process.
You have to chase pointers and recurse into embedded structures.
In real programs inspecting structures using reflections would be recursive.
type Inner struct {
N int
}
type S struct {
Inner
NamedInner Inner
PtrInner *Inner
unexported int
N int8
}
func indentStr(level int) string {
return strings.Repeat(" ", level)
}
// if sf is not nil, this is a field of a struct
func describeStruct(level int, rv reflect.Value, sf *reflect.StructField) {
structType := rv.Type()
nFields := rv.NumField()
typ := rv.Type()
if sf == nil {
fmt.Printf("%sstruct %s, %d field(s), size: %d bytes\n", indentStr(level), structType.Name(), nFields, typ.Size())
} else {
fmt.Printf("%sname: '%s' type: 'struct %s', offset: %d, %d field(s), size: %d bytes, embedded: %v\n", indentStr(level), sf.Name, structType.Name(), sf.Offset, nFields, typ.Size(), sf.Anonymous)
}
for i := 0; i < nFields; i++ {
fv := rv.Field(i)
sf := structType.Field(i)
describeType(level+1, fv, &sf)
}
}
// if sf is not nil, this is a field of a struct
func describeType(level int, rv reflect.Value, sf *reflect.StructField) {
switch rv.Kind() {
case reflect.Int, reflect.Int8:
// in real code we would handle more primitive types
i := rv.Int()
typ := rv.Type()
if sf == nil {
fmt.Printf("%stype: '%s', value: '%d'\n", indentStr(level), typ.Name(), i)
} else {
fmt.Printf("%s name: '%s' type: '%s', value: '%d', offset: %d, size: %d\n", indentStr(level), sf.Name, typ.Name(), i, sf.Offset, typ.Size())
}
case reflect.Ptr:
fmt.Printf("%spointer\n", indentStr(level))
describeType(level+1, rv.Elem(), nil)
case reflect.Struct:
describeStruct(level, rv, sf)
}
}
func main() {
var s S
describeType(0, reflect.ValueOf(s), nil)
}
struct S, 5 field(s), size: 40 bytes
name: 'Inner' type: 'struct Inner', offset: 0, 1 field(s), size: 8 bytes, embedded: true
name: 'N' type: 'int', value: '0', offset: 0, size: 8
name: 'NamedInner' type: 'struct Inner', offset: 8, 1 field(s), size: 8 bytes, embedded: false
name: 'N' type: 'int', value: '0', offset: 0, size: 8
pointer
name: 'unexported' type: 'int', value: '0', offset: 24, size: 8
name: 'N' type: 'int8', value: '0', offset: 32, size: 1
Found a mistake? Have a question or improvement idea?
Let me know.
Table Of Contents