맵을 구조체로 변환


94

.NET Framework에서 struct사용 데이터를 채울 Go에서 일반 메서드를 만들려고 합니다 map[string]interface{}. 예를 들어, 메소드 서명 및 사용법은 다음과 같습니다.

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

JSON을 중개자로 사용하여이 작업을 수행 할 수 있다는 것을 알고 있습니다. 이 작업을 수행하는 또 다른 더 효율적인 방법이 있습니까?


1
JSON을 중개자로 사용하면 어쨌든 리플렉션을 사용합니다. stdlib encoding/json패키지를 사용하여 중간 단계를 수행 한다고 가정합니다 .이 메서드를 사용할 수있는 예제 맵과 예제 구조체를 제공 할 수 있습니까?
Simon Whitehead

네, 이것이 제가 JSON을 피하려는 이유입니다. 내가 모르는 더 효율적인 방법이 있기를 바랍니다.
tgrosinger 2014

예제 사용 사례를 제공 할 수 있습니까? 에서와 같이-이 메서드가 수행 할 작업을 보여주는 의사 코드를 보여 줍니까?
Simon Whitehead

음 ... unsafe패키지에 방법이 있을지도 몰라 ..하지만 감히 해보지 않아. 데이터를 속성에 배치하려면 유형과 관련된 메타 데이터를 쿼리 할 수 ​​있어야하므로 .. 리플렉션이 필요합니다. 이것을 json.Marshal+ json.Decode콜로 래핑하는 것은 상당히 간단 할 것입니다 . 그러나 그것은 반사의 두 배입니다.
Simon Whitehead 2014

반성에 대한 의견을 삭제했습니다. 저는이 작업을 가능한 한 효율적으로 수행하는 데 더 관심이 있습니다. 그것이 반사를 사용하는 것을 의미한다면 괜찮습니다.
tgrosinger 2014

답변:


113

가장 간단한 방법은 https://github.com/mitchellh/mapstructure 를 사용하는 것입니다.

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

직접하고 싶다면 다음과 같이 할 수 있습니다.

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
감사합니다. 약간 수정 된 버전을 사용하고 있습니다. play.golang.org/p/_JuMm6HMnU
tgrosinger 2014

모든 다양한 구조체에 대해 FillStruct 동작을 원하고 모든 구조체에 대해 정의 할 func (s MyStr...) FillStruct ...필요 가 없습니다 . 기본 구조체에 대해 FillStruct를 정의한 다음 다른 모든 구조체가 해당 동작을 '상속'하도록 할 수 있습니까? 위의 패러다임에서는 기본 구조체 만 있기 때문에 가능하지 않습니다.이 경우 "MyStruct"는 실제로 필드가 반복됩니다.
StartupGuy


Mystruct에서 태그를 구현할 수 있습니까?
vicTROLLA

1
@abhishek 첫 번째 텍스트 마샬링과 마샬링 해제에 대해 지불해야하는 성능 저하가 확실히 있습니다. 그 접근 방식도 확실히 더 간단합니다. 이것은 절충안이며 일반적으로 더 간단한 솔루션을 사용합니다. "JSON을 중개자로 사용하여이 작업을 수행 할 수 있다는 것을 알고 있습니다.이 작업을 수행하는 또 다른 효율적인 방법이 있습니까?"라는 질문에이 솔루션으로 대답했습니다. 이 솔루션은 더 효율적이고 JSON 솔루션은 일반적으로 구현 및 추론하기가 더 쉽습니다.
dave

74

Hashicorp의 https://github.com/mitchellh/mapstructure 라이브러리는이 작업을 즉시 수행합니다.

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

두 번째 result매개 변수는 구조체의 주소 여야합니다.


맵 키가 user_name있고 struct 파일이 UserName있다면 어떨까요?
Nicholas Jela

1
@NicholasJela는 태그와 그 처리 할 수 godoc.org/github.com/mitchellh/mapstructure#ex-Decode--Tags
회로를 벽에

맵 kye가 _id이고 맵 이름이 Id이면 디코딩하지 않습니다.
Ravi Shankar

27
  • 이를 수행하는 가장 간단한 방법은 encoding/json패키지를 사용하는 것입니다.

예를 들면 :

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
감사합니다 @jackytse. 이것은 실제로 그렇게하는 가장 좋은 방법입니다 !! 지도 구조는 인터페이스 내의 중첩 된지도에서 자주 작동하지 않습니다. 따라서 맵 문자열 인터페이스를 고려하고 json으로 처리하는 것이 좋습니다.
Gilles Essoki

위의 스 니펫에 대한 Go 놀이터 링크 : play.golang.org/p/JaKxETAbsnT
Junaid

13

당신은 그것을 할 수 있습니다 ... 그것은 약간 추악해질 수 있으며 매핑 유형 측면에서 시행 착오에 직면하게 될 것입니다 .. 그러나 여기에 기본적인 요점이 있습니다.

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))
    }
}

작업 샘플 : http://play.golang.org/p/PYHz63sbvL


1
이것은 0 값에 당황하는 것처럼 보입니다.reflect: call of reflect.Value.Set on zero Value
James Taylor

@JamesTaylor 예. 내 대답은 매핑하는 필드를 정확히 알고 있다고 가정합니다. 더 많은 오류 처리 (발생한 오류 포함)가 포함 된 유사한 답변을 찾고 있다면 대신 Daves 답변을 제안합니다.
Simon Whitehead

2

dave의 대답을 수정하고 재귀 기능을 추가합니다. 나는 여전히 더 사용자 친화적 인 버전을 작업 중입니다. 예를 들어 맵의 숫자 문자열은 구조체에서 int로 변환 할 수 있어야합니다.

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

두 단계가 있습니다.

  1. 인터페이스를 JSON 바이트로 변환
  2. JSON 바이트를 구조체로 변환

다음은 예입니다.

dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.