Как вы прокручиваете поля в структуре Golang, чтобы получать и устанавливать значения расширяемым способом?

У меня есть структура Person.

type Person struct {
    Firstname string       
    Lastname  string       
    Years     uint8       
}

Затем у меня есть два экземпляра этой структуры, PersonA и PersonB.

PersonA := {"", "Obama", 6}
PersonB := {"President", "Carter", 8}

Я хочу написать функцию, которая копирует значения из PersonA в PersonB, учитывая некоторые условия для каждого поля (т.е. не пустые). Я знаю, как сделать это путем жесткого кодирования имен полей, но мне нужна функция, которая работает, даже если я изменяю структуру Person.

Я знаю, что отражения Go полезны, но проблема в получении и установке значений требует знания типов, если вы хотите использовать что-то вроде SetInt. Но есть ли "простой" способ сделать это?

** Аналогия с Javascript ** В Javascript вы можете просто выполнить (для свойства в someObject) цикл.

(for propt in personA) {
  if personA[propt] != "" {
    // do something
    personB[propt] = personA[propt]
  }
}

Опции, которые я исключил:

  1. Отслеживание полей в каждой структуре на карте с последующим использованием комбинации FieldByName и коллекции функций Set * в отражающем пакете.

  2. Создание цикла по полям Person вручную (ниже). Потому что я хочу сделать этот тип "обновления" для многих других структур (школа, животные и т.д.)

    if PersonA.Firstname != "" {
      PersonB.Firstname = PersonA.Firstname 
    }
    

    ...

    if PersonA.Years != "" {
      PersonB.Years = PersonA.Years 
    }
    

Приведенный ниже вопрос ставит меня на полпути, но все еще не распространяется на все структуры, для которых я хочу использовать эту функцию "обновления".

в Голанге, используя отражение, как вы устанавливаете значение поля структуры?

** Другие полезные ссылки ** GoLang: Доступ к свойству struct по имени

Ответ 1

Используйте reflect.ValueOf() для преобразования в конкретный тип. После этого вы можете использовать refle.Value.SetString, чтобы установить желаемое значение.

structValue := FooBar{Foo: "foo", Bar: 10}
fields := reflect.TypeOf(structValue)
values := reflect.ValueOf(structValue)

num := fields.NumField()

for i := 0; i < num; i++ {
    field := fields.Field(i)
    value := values.Field(i)
    fmt.Print("Type:", field.Type, ",", field.Name, "=", value, "\n")

    switch value.Kind() {
    case reflect.String:
        v := value.String())
        fmt.Print(v, "\n")
    case reflect.Int:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    case reflect.Int32:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    case reflect.Int64:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    default:
        assert.Fail(t, "Not support type of struct")
    }
}

Ответ 2

Вот решение f2.Set(reflect.Value(f)) - это ключ здесь

package main

   import (
    "fmt"
    "reflect"
   )

   func main() {
    type T struct {
        A int
        B string
    }
    t := T{23, "skidoo"}
    t2:= T{}
    s := reflect.ValueOf(&t).Elem()
    s2 := reflect.ValueOf(&t2).Elem()
    typeOfT := s.Type()
    fmt.Println("t=",t)
    fmt.Println("t2=",t2)

    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        f2:= s2.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f.Type(), f.Interface())
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f2.Type(), f2.Interface())
        f2.Set(reflect.Value(f))
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f2.Type(), f2.Interface())

    }
    fmt.Println("t=",t)
    fmt.Println("t2=",t2)
}

Output:

t= {23 skidoo}
t2= {0 }
0: A int = 23
0: A int = 0
0: A int = 23
1: B string = skidoo
1: B string = 
1: B string = skidoo
t= {23 skidoo}
t2= {23 skidoo}

http://play.golang.org/p/UKFMBxfbZD

Ответ 3

Отражение должно быть всем, что вам нужно. Это похоже (хотя и не идентично) на семантику "глубокого копирования", которая была реализована на https://godoc.org/github.com/getlantern/deepcopy

Вы должны быть в состоянии приспособить это к вашим потребностям или, по крайней мере, взять некоторые идеи из этого.

Ответ 4

Вместо этого вы должны использовать map[string]interface{}, гораздо быстрее (хотя и не так быстро, как вы использовали правильную логику с фактическими структурами).

package main

import "fmt"

type Object map[string]interface{}

var m = Object{
    "Firstname": "name",
    "Lastname":  "",
    "years":     uint8(10),
}

func main() {
    var cp = Object{}
    for k, v := range m {
        if s, ok := v.(string); ok && s != "" {
            cp[k] = s
        } else if ui, ok := v.(uint8); ok {
            cp[k] = ui
        }
    }
    fmt.Printf("%#v\n", cp)
}