Я хочу проверить, реализует ли полученный элемент типа any
encoding.TextUnmarshaler
, чтобы я мог вызвать UnmarshalText
для него. Проблема в том, что его метод должен быть приемником указателя, и это, похоже, является источником некоторых проблем.
import (
"encoding"
"github.com/stretchr/testify/assert"
"reflect"
"testing"
)
type MyStruct struct{ value string }
var _ encoding.TextMarshaler = (*MyStruct)(nil)
func (id MyStruct) MarshalText() (text []byte, err error) {
return []byte(id.value), nil
}
var _ encoding.TextUnmarshaler = (*MyStruct)(nil)
func (id *MyStruct) UnmarshalText(data []byte) error {
*id = MyStruct{value: string(data)}
return nil
}
func TestIfICouldInvokeUnmarshalText(t *testing.T) {
var ms any = MyStruct{value: "hello"}
_, ok := (ms).(encoding.TextMarshaler)
assert.True(t, ok)
_, ok = (ms).(encoding.TextUnmarshaler)
//assert.True(t, ok) // this is the first attempt, which will fail
v := reflect.ValueOf(ms)
if assert.True(t, v.Type().NumMethod() > 0 && v.CanInterface()) {
if v.CanAddr() {
_, ok := v.Addr().Interface().(encoding.TextUnmarshaler)
assert.True(t, ok)
} else {
t.FailNow() // this is where I end up
}
}
}
Есть ли какой-нибудь (идиоматический) способ добиться этого?
Вы хотите проверить, реализуется ли значение encoding.TextUnmarshaler
и является ли оно указателем. Ты можешь написать:
func IsUnmarshalerPointer(ms any) bool {
_, ok := ms.(encoding.TextUnmarshaler)
return ok && reflect.ValueOf(ms).Kind() == reflect.Pointer
}
Во-первых,
var _ encoding.TextUnmarshaler = (*MyStruct)(nil)
гарантирует, что *MyStruct
реализует encoding.TextUnmarshaler
, так зачем вам это проверять?
Во-вторых, MyStruct
не реализует encoding.TextUnmarshaler
, и в расширении:
var ms any = MyStruct{value: "hello"}
нет, так что там все правильно.
Причина, по которой MyStruct
не реализуется encoding.TextUnmarshaler
, заключается в том, что любой его метод не может изменить исходную структуру, поскольку он работает с копией, поэтому любые изменения будут потеряны.
У вас уже есть копия в вашем интерфейсе, поэтому ссылка на исходную структуру все равно теряется (Go Playground):
package main
import (
"fmt"
)
type MyValue struct{ Value string }
func (m MyValue) SetValue(value string) {
m.Value = value
}
func main() {
m := MyValue{"hello"}
a := any(m)
m.SetValue("world")
a.(MyValue).SetValue("goodbye")
fmt.Println(m, a)
m.Value = "world"
fmt.Println(m, a)
}
принты
{hello} {hello}
{world} {hello}
у вас уже другие ценности. В качестве примечания: a.(MyValue).Value = ...
не компилируется, поскольку a.(MyValue).Value
не адресуется.
*MyStruct
реализует encoding.TextUnmarshaler
, так что попробуйте
var ms any = &MyStruct{value: "hello"}
Редактировать:
Если вы попробуете какой-нибудь трюк, вас ждет сюрприз (Go Playground):
package main
import (
"encoding"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
type MyStruct struct{ value string }
var _ encoding.TextMarshaler = (*MyStruct)(nil)
func (id MyStruct) MarshalText() (text []byte, err error) {
return []byte(id.value), nil
}
var _ encoding.TextUnmarshaler = (*MyStruct)(nil)
func (id *MyStruct) UnmarshalText(data []byte) error {
*id = MyStruct{value: string(data)}
return nil
}
func TestIfICouldInvokeUnmarshalText(t *testing.T) {
var ms any = MyStruct{value: "hello"}
_, ok := (ms).(encoding.TextMarshaler)
if !ok {
t.FailNow()
}
_, ok = (ms).(encoding.TextUnmarshaler)
assert.False(t, ok)
v := reflect.Indirect(reflect.New(reflect.TypeOf(ms)))
v.Set(reflect.ValueOf(ms)) // makes an addressable copy
sp := v.Addr().Interface()
u, ok := (sp).(encoding.TextUnmarshaler)
if !ok {
t.FailNow()
}
_ = u.UnmarshalText([]byte("world"))
m, _ := ms.(MyStruct)
assert.Equal(t, "world", m.value)
}
Да, вы можете вызвать UnmarshalText
для копии вашей структуры, но результаты будут потеряны, и ваша исходная переменная по-прежнему будет содержать «привет» вместо «мир».
Я думаю, что вы хотите сделать что-то вроде этого:
var ms any = MyStruct{value: "hello"}
_, ok := (ms).(encoding.TextMarshaler)
if !ok {
t.FailNow()
}
_, ok = (ms).(encoding.TextUnmarshaler)
//assert.True(t, ok) // this is the first attempt, which will fail
v := reflect.Indirect(reflect.New(reflect.TypeOf(ms)))
v.Set(reflect.ValueOf(ms))
sp := v.Addr().Interface()
_, ok = (sp).(encoding.TextUnmarshaler)
if !ok {
t.FailNow()
}
Это не делает то, что вы думаете. Вы вызываете TextUnmarshaler
не на «этом», а на копии (Go Playgound).
Вероятно, вы захотите проверить, что переданное значение A) реализует некоторый метод и B) является указателем. А) делается так же, как и утверждение типа, и Б) делается просто так: с помощью отражения проверяется, что это указатель. Нет необходимости проверять, что это указатель и реализует что-то, поскольку это уже проверено A.