30

Is there a way to use reflect to access unexported fields in Go 1.8? This no longer seems to work: https://stackoverflow.com/a/17982725/555493

Note that reflect.DeepEqual works just fine (that is, it can access unexported fields) but I can't make heads or tails of that function. Here's a go playarea that shows it in action: https://play.golang.org/p/vyEvay6eVG. The src code is below

import (
"fmt"
"reflect"
)

type Foo struct {
  private string
}

func main() {
    x := Foo{"hello"}
    y := Foo{"goodbye"}
    z := Foo{"hello"}

    fmt.Println(reflect.DeepEqual(x,y)) //false
    fmt.Println(reflect.DeepEqual(x,z)) //true
}
icza
  • 389,944
  • 63
  • 907
  • 827
U Avalos
  • 6,538
  • 7
  • 48
  • 81

4 Answers4

56

If the struct is addressable, you can use unsafe.Pointer to access the field (read or write) it, like this:

rs := reflect.ValueOf(&MyStruct).Elem()
rf := rs.Field(n)
// rf can't be read or set.
rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem()
// Now rf can be read and set.

See full example on the playground.

This use of unsafe.Pointer is valid according to the documentation and running go vet returns no errors.

If the struct is not addressable this trick won't work, but you can create an addressable copy like this:

rs = reflect.ValueOf(MyStruct)
rs2 := reflect.New(rs.Type()).Elem()
rs2.Set(rs)
rf = rs2.Field(0)
rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem()
// Now rf can be read.  Setting will succeed but only affects the temporary copy.

See full example on the playground.

mattes
  • 8,936
  • 5
  • 48
  • 73
cpcallen
  • 1,834
  • 1
  • 16
  • 27
  • 1
    Brilliant trick, thank you. Combined with `FieldByName`, this is quite powerful, albeit dirty. – user1112789 Aug 23 '17 at 13:17
  • what is meant by the "struct is addressable"? –  Feb 08 '20 at 08:27
  • 3
    @rakim: it means it you can take the address of it—e.g., it's a local variable or on the heap—rather than a temporary value such as the return value of a function. You can read more here: https://utcc.utoronto.ca/~cks/space/blog/programming/GoAddressableValues – cpcallen Feb 09 '20 at 12:07
23

Based on cpcallen's work:

import (
    "reflect"
    "unsafe"
)

func GetUnexportedField(field reflect.Value) interface{} {
    return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface()
}

func SetUnexportedField(field reflect.Value, value interface{}) {
    reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).
        Elem().
        Set(reflect.ValueOf(value))
}


reflect.NewAt might be confusing to read at first. It returns a reflect.Value representing a pointer to a value of the specified field.Type(), using unsafe.Pointer(field.UnsafeAddr()) as that pointer. In this context reflect.NewAt is different than reflect.New, which would return a pointer to a freshly initialized value.

Example:

type Foo struct {
    unexportedField string
}

GetUnexportedField(reflect.ValueOf(&Foo{}).Elem().FieldByName("unexportedField"))

https://play.golang.org/p/IgjlQPYdKFR

mattes
  • 8,936
  • 5
  • 48
  • 73
  • That worked perfect for me. The code exaples are more structured, than in the [answer of cpcallen](https://stackoverflow.com/a/43918797/2894081) . – Pokulo Feb 24 '23 at 16:34
6

reflect.DeepEqual() can do it because it has access to unexported features of the reflect package, in this case namely for the valueInterface() function, which takes a safe argument, which denies access to unexported field values via the Value.Interface() method if safe=true. reflect.DeepEqual() will (might) call that passing safe=false.

You can still do it, but you cannot use Value.Interface() for unexported fields. Instead you have to use type-specific methods, such as Value.String() for string, Value.Float() for floats, Value.Int() for ints etc. These will return you a copy of the value (which is enough to inspect it), but will not allow you to modify the field's value (which might be "partly" possible if Value.Interface() would work and the field type would be a pointer type).

If a field happens to be an interface type, you may use Value.Elem() to get to the value contained / wrapped by the interface value.

To demonstrate:

type Foo struct {
    s string
    i int
    j interface{}
}

func main() {
    x := Foo{"hello", 2, 3.0}
    v := reflect.ValueOf(x)

    s := v.FieldByName("s")
    fmt.Printf("%T %v\n", s.String(), s.String())

    i := v.FieldByName("i")
    fmt.Printf("%T %v\n", i.Int(), i.Int())

    j := v.FieldByName("j").Elem()
    fmt.Printf("%T %v\n", j.Float(), j.Float())
}

Output (try it on the Go Playground):

string hello
int64 2
float64 3
icza
  • 389,944
  • 63
  • 907
  • 827
  • Ok but how can you know what the type is so that you can call the right method? – U Avalos Mar 08 '17 at 14:39
  • @UAvalos E.g. by calling [`Value.Type()`](https://golang.org/pkg/reflect/#Value.Type) (on the field's value). – icza Mar 08 '17 at 14:40
  • wouldn't that result in a really painful switch block? ex: case Uint, case Uint8, Uint16, etc... – U Avalos Mar 08 '17 at 21:25
  • @UAvalos If you want to handle all types, unfortunately yes. If you just want to print its value, you may simply use `Value.String()` which does this inside it. Also note that `Value.Int()` for example handles all signed integer types (e.g. `int8`, `int16`, `int32`, `int64` and `int`) so these help you "ease" the pain. – icza Mar 09 '17 at 02:50
  • Value.String() only returns the value if its a string. Otherwise it returns the string of the type of the value. – nhooyr Feb 04 '19 at 06:35
  • As @icza notes, the pain is not *too* painful. See https://play.golang.org/p/uB5KJYZzR5v for a sample that started with the [Laws of Reflection blog post](https://blog.golang.org/laws-of-reflection), to which I added some more accessors for private fields. – torek Aug 10 '19 at 00:07
0
package main

import (
    "fmt"
    "reflect"
    "strings"
    "unsafe"
)

type Person1 struct {
    W3ID string
    Name string
}

type Address1 struct {
    city    string
    country string
}

type User1 struct {
    name      string
    age       int
    address   Address1
    manager   Person1
    developer Person1
    tech      Person1
}

func showDetails(load, email interface{}) {
    if reflect.ValueOf(load).Kind() == reflect.Struct {
        typ := reflect.TypeOf(load)
        value := reflect.ValueOf(load)
        //#1 For struct, not addressable create a copy With Element.
        value2 := reflect.New(value.Type()).Elem() 
        //#2 Value2 is addressable and can be set
        value2.Set(value)     
        for i := 0; i < typ.NumField(); i++ {
            if value.Field(i).Kind() == reflect.Struct {
                rf := value2.Field(i)
                /* #nosec G103 */
                rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem()
                irf := rf.Interface()
                typrf := reflect.TypeOf(irf)
                nameP := typrf.String()
                if strings.Contains(nameP, "Person") {
                    //fmt.Println(nameP, "FOUND !!!!!!! ")
                    for j := 0; j < typrf.NumField(); j++ {
                        re := rf.Field(j)
                        nameW := typrf.Field(j).Name
                        if strings.Contains(nameW, "W3ID") {
                            valueW := re.Interface()
                            fetchEmail := valueW.(string)
                            if fetchEmail == email {
                                fmt.Println(fetchEmail, " MATCH!!!!")
                            }
                        }
                    }
                }
                showDetails(irf, email)
            } else {
                // fmt.Printf("%d.Type:%T || Value:%#v\n",
                //  (i + 1), value.Field(i), value.Field(i))
            }
        }
    }
}

func main() {
    iD := "tsumi@in.org.com"

    load := User1{
        name: "John Doe",
        age:  34,
        address: Address1{
            city:    "New York",
            country: "USA",
        },
        manager: Person1{
            W3ID: "jBult@in.org.com",
            Name: "Bualt",
        },
        developer: Person1{
            W3ID: "tsumi@in.org.com",
            Name: "Sumi",
        },
        tech: Person1{
            W3ID: "lPaul@in.org.com",
            Name: "Paul",
        },
    }

    showDetails(load, iD)
}