Haritayı yapıya dönüştürme


95

Go'da struct, bir map[string]interface{}. Örneğin, yöntem imzası ve kullanımı şöyle görünebilir:

func FillStruct(data map[string]interface{}, result interface{}) {
    ...
}

type MyStruct struct {
    Name string
    Age  int64
}

myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"]  = 23

result := &MyStruct{}
FillStruct(myData, result)

// result now has Name set to "Tony" and Age set to 23

Bunun JSON aracılığı ile yapılabileceğini biliyorum; bunu yapmanın daha verimli başka bir yolu var mı?


1
JSON'u bir aracı olarak kullanmak, yine de yansıma kullanacaktır. encoding/jsonBu ara adımı gerçekleştirmek için stdlib paketini kullanacağınızı varsayarsak .. Bu yöntemin kullanılabileceği bir örnek harita ve örnek yapı verebilir misiniz?
Simon Whitehead

Evet, JSON'dan kaçınmaya çalışmamın nedeni bu. Umarım bilmediğim daha verimli bir yöntem var gibi görünüyor.
tgrosinger

Örnek bir kullanım durumu verebilir misiniz? Olduğu gibi - bu yöntemin ne yapacağını gösteren bazı sözde kodlar gösterin.
Simon Whitehead

Mmm ... unsafepaketin bir yolu olabilir .. ama denemeye cesaret edemem. Bunun dışında .. Bir türle ilişkili meta verileri, özelliklerine veri yerleştirmek için sorgulayabilmeniz gerektiğinden yansıtma gereklidir. Bunu json.Marshal+ json.Decodeçağrılara sarmak oldukça basit olurdu .. ama bu yansımayı iki katına çıkarır.
Simon Whitehead

Yansıma hakkındaki yorumumu kaldırdım. Bunu olabildiğince verimli bir şekilde yapmakla daha çok ilgileniyorum. Eğer bu yansıma kullanmak anlamına geliyorsa sorun değil.
tgrosinger

Yanıtlar:


115

En basit yol https://github.com/mitchellh/mapstructure kullanmak olacaktır.

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

Kendin yapmak istiyorsan, şöyle bir şey yapabilirsin:

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

func SetField(obj interface{}, name string, value interface{}) error {
    structValue := reflect.ValueOf(obj).Elem()
    structFieldValue := structValue.FieldByName(name)

    if !structFieldValue.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }

    if !structFieldValue.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    structFieldType := structFieldValue.Type()
    val := reflect.ValueOf(value)
    if structFieldType != val.Type() {
        return errors.New("Provided value type didn't match obj field type")
    }

    structFieldValue.Set(val)
    return nil
}

type MyStruct struct {
    Name string
    Age  int64
}

func (s *MyStruct) FillStruct(m map[string]interface{}) error {
    for k, v := range m {
        err := SetField(s, k, v)
        if err != nil {
            return err
        }
    }
    return nil
}

func main() {
    myData := make(map[string]interface{})
    myData["Name"] = "Tony"
    myData["Age"] = int64(23)

    result := &MyStruct{}
    err := result.FillStruct(myData)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(result)
}

1
Teşekkür ederim. Biraz değiştirilmiş bir versiyon kullanıyorum. play.golang.org/p/_JuMm6HMnU
tgrosinger

FillStruct davranışını tüm çeşitli yapılarımda istiyorum ve func (s MyStr...) FillStruct ...her biri için tanımlamak zorunda değilim . FillStruct'ı bir temel yapı için tanımlamak, sonra diğer tüm yapılarımın bu davranışı 'devralması' mümkün müdür? Yukarıdaki paradigmada, bu mümkün değildir çünkü sadece temel yapı ... bu durumda "MyStruct" aslında alanlarını yinelemeye sahip olacaktır
StartupGuy

Demek istediğim, bunun gibi herhangi bir yapı için çalışmasını sağlayabilirsiniz: play.golang.org/p/0weG38IUA9
dave

Mystruct'ta etiketleri uygulamak mümkün mü?
vicTROLLA

1
@abhishek kesinlikle ilk metne sıralamak ve sonra eşleştirmeyi kaldırmak için ödeyeceğiniz bir performans cezası vardır. Bu yaklaşım da kesinlikle daha basit. Bu bir değiş tokuş ve genellikle daha basit bir çözümle giderim. Bu çözümle cevap verdim çünkü "Bunun JSON aracılığı ile yapılabileceğini biliyorum; bunu yapmanın daha verimli başka bir yolu var mı?" Bu çözüm daha verimli olacak, JSON çözümünün uygulanması genellikle daha kolay olacak ve mantıklı olacaktır.
dave

74

Hashicorp'un https://github.com/mitchellh/mapstructure kitaplığı bunu kutudan çıkarır:

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

İkinci resultparametre yapının bir adresi olmalıdır.


ya harita anahtarı ise user_nameve yapı dosyalanmışsa UserName?
Nicholas Jela


ya harita kye _id ise ve harita adı Id ise, o zaman onu çözmeyecektir.
Ravi Shankar

27
  • bunu yapmanın en basit yolu encoding/jsonpaket kullanmaktır

sadece örneğin:

package main
import (
    "fmt"
    "encoding/json"
)

type MyAddress struct {
    House string
    School string
}
type Student struct {
    Id int64
    Name string
    Scores float32
    Address MyAddress
    Labels []string
}

func Test() {

    dict := make(map[string]interface{})
    dict["id"] = 201902181425       // int
    dict["name"] = "jackytse"       // string
    dict["scores"] = 123.456        // float
    dict["address"] = map[string]string{"house":"my house", "school":"my school"}   // map
    dict["labels"] = []string{"aries", "warmhearted", "frank"}      // slice

    jsonbody, err := json.Marshal(dict)
    if err != nil {
        // do error check
        fmt.Println(err)
        return
    }

    student := Student{}
    if err := json.Unmarshal(jsonbody, &student); err != nil {
        // do error check
        fmt.Println(err)
        return
    }

    fmt.Printf("%#v\n", student)
}

func main() {
    Test()
}

1
Teşekkürler @jackytse. Aslında bunu yapmanın en iyi yolu bu !! harita yapısı genellikle bir arabirim içindeki iç içe geçmiş haritayla çalışmaz. Bu yüzden, bir harita dizisi arayüzünü düşünmek ve onu bir json olarak kullanmak daha iyi.
Gilles Essoki

Yukarıdaki snippet için oyun alanı bağlantısına gidin: play.golang.org/p/JaKxETAbsnT
Junaid

13

Yapabilirsiniz ... biraz çirkinleşebilir ve haritalama türleri açısından bazı deneme yanılma ile karşı karşıya kalabilirsiniz .. ama işte temel özü:

func FillStruct(data map[string]interface{}, result interface{}) {
    t := reflect.ValueOf(result).Elem()
    for k, v := range data {
        val := t.FieldByName(k)
        val.Set(reflect.ValueOf(v))
    }
}

Çalışma örneği: http://play.golang.org/p/PYHz63sbvL


1
Bu, sıfır değerlerde paniğe yol reflect: call of reflect.Value.Set on zero Value
James Taylor

@JamesTaylor Evet. Cevabım, hangi alanları eşleştirdiğinizi tam olarak bildiğinizi varsayar. Daha fazla hata işleme içeren benzer bir cevabın peşindeyseniz (yaşadığınız hata dahil), bunun yerine Daves cevabını öneririm.
Simon Whitehead

2

Dave'in cevabını uyarladım ve özyinelemeli bir özellik ekledim. Hala daha kullanıcı dostu bir sürüm üzerinde çalışıyorum. Örneğin, haritadaki bir sayı dizgisi yapı içinde int türüne dönüştürülebilmelidir.

package main

import (
    "fmt"
    "reflect"
)

func SetField(obj interface{}, name string, value interface{}) error {

    structValue := reflect.ValueOf(obj).Elem()
    fieldVal := structValue.FieldByName(name)

    if !fieldVal.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }

    if !fieldVal.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    val := reflect.ValueOf(value)

    if fieldVal.Type() != val.Type() {

        if m,ok := value.(map[string]interface{}); ok {

            // if field value is struct
            if fieldVal.Kind() == reflect.Struct {
                return FillStruct(m, fieldVal.Addr().Interface())
            }

            // if field value is a pointer to struct
            if fieldVal.Kind()==reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct {
                if fieldVal.IsNil() {
                    fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
                }
                // fmt.Printf("recursive: %v %v\n", m,fieldVal.Interface())
                return FillStruct(m, fieldVal.Interface())
            }

        }

        return fmt.Errorf("Provided value type didn't match obj field type")
    }

    fieldVal.Set(val)
    return nil

}

func FillStruct(m map[string]interface{}, s interface{}) error {
    for k, v := range m {
        err := SetField(s, k, v)
        if err != nil {
            return err
        }
    }
    return nil
}

type OtherStruct struct {
    Name string
    Age  int64
}


type MyStruct struct {
    Name string
    Age  int64
    OtherStruct *OtherStruct
}



func main() {
    myData := make(map[string]interface{})
    myData["Name"]        = "Tony"
    myData["Age"]         = int64(23)
    OtherStruct := make(map[string]interface{})
    myData["OtherStruct"] = OtherStruct
    OtherStruct["Name"]   = "roxma"
    OtherStruct["Age"]    = int64(23)

    result := &MyStruct{}
    err := FillStruct(myData,result)
    fmt.Println(err)
    fmt.Printf("%v %v\n",result,result.OtherStruct)
}

1

İki adım vardır:

  1. Arayüzü JSON Byte'a dönüştür
  2. JSON Baytını yapıya dönüştür

Aşağıda bir örnek verilmiştir:

dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.