dongqigu0429
2015-04-01 20:23
浏览 232
已采纳

Golang抽象接口切片转换

I have a list of objects (olievere/Elastic SearchResult.Hits to be exact). Each of these has a json.RawMessage object and I'm looking to create an abstractable method that takes in an interface slice of any struct, Unmarshal's each individual hits' json.RawMessage into said struct, and appends it to the passed in []interface.

This func is not supposed to have any logic or insight into the desired business layer struct, and the DB Call is interfaced pretty heavily, and as such has no visibility into the Elastic package mentioned above. Example of what I was attempting to do...

foo.go    
import (bar, package)
type TestStruct struct {    
    Slice []*package.Struct // package.Struct has a value of Source which is a    
                            // json.RawMessage    
}    

func GetData() bar.Test {
    return &TestStruct{*package.GetData()}
}

func (result TestStruct) UnmarshalStruct(v []interface{}) {    
    for _, singleStruct := range result.Slice {     
        append(json.Unmarshal(singleStruct, &v))
    }

Second File

bar.go
type Handler interface {
    GetData() Test
}

type Test interface {
    UnmarshalStruct
}

type OtherType struct {
   foo string
   bar string
} 

func RetrieveData() []OtherType {
    handler := New(Handler)
    test := handler.GetData()
    var typeSlice []OtherType    
    test.UnmarshalStruct(&typeSlice)
}

I'm looking to hand something of type []OtherType, or any other new struct I decide to create, to UnmarshalStruct, and have it return to me that same struct, just full of data

As an example, I have two different types of data I'll be searching for from Elastic. I will be receiving a list of ONE of the following two objects.

{ 'foo': '',
  'id': 
}

And in a different index

{ 'bar': '',
  'baz': '',
  'eee': ''
}     

Each of these will naturally require two different structs.
However, I desire a single method to be able to decode either of these lists. I'll be given below, and using the same function I want to be able to convert this to a bar struct, and the other type to a foo struct.

{ 'source': [
    { 'bar': '',
      'baz': '',
      'eee': ''
    },
    { 'bar': '',
      'baz': '',
      'eee': ''
    },
    { 'bar': '',
      'baz': '',
      'eee': ''
    }    
  ]
}
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

3条回答 默认 最新

  • douyou2048 2015-04-02 14:33
    已采纳

    There's really no way to do what you want without reflection. I would personally structure this differently, so that you unmarshal into more generic types, like a map[string]string, or as @ThunderCat shows, get rid of the intermediary state and unamrshal directly into the correct types. But it can be done...

    (I moved the json.RawMessage directly into TestStruct to get rid of one level of indirection and make the example more clear)

    type TestStruct struct {
        Slice []json.RawMessage
    }
    
    func (t TestStruct) UnmarshalStruct(v interface{}) error {
        // get the a Value for the underlying slice
        slice := reflect.ValueOf(v).Elem()
        // make sure we have adequate capacity
        slice.Set(reflect.MakeSlice(slice.Type(), len(t.Slice), len(t.Slice)))
    
        for i, val := range t.Slice {
            err := json.Unmarshal(val, slice.Index(i).Addr().Interface())
            if err != nil {
                return err
            }
        }
    
        return nil
    }
    

    You can then call it like so

    var others []OtherType
    err := ts.UnmarshalStruct(&others)
    if err != nil {
        log.Fatal(err)
    }
    

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

    已采纳该答案
    打赏 评论
  • dongyan2469 2015-04-02 01:29

    If I understand correctly, you want to unmarshal data to slices of two types:

    type A struct {
      Foo string `json:"foo"`
      ID string `json:"id"`
    }
    
    type B struct {
       Bar string `json:"bar"`
       Baz string `json:"baz"`
       Eee string `json:"eee"`
    }
    

    from a SearchHit Source.

    The JSON package can do most of the work for you:

    func executeQuery(q Query, v interface{}) error {
       // Get a SearchHit. I am making this up. 
       // I have no idea how the package works.
       searchHit, err := getHit(q) 
       if err != nil {
          return err
       }
       // This is the important part. Convert the raw message to 
       // a slice of bytes and decode to the caller's slice.
       return json.Unmarshal([]byte(*searchHit.Source), v)
    }
    

    You can call this function to decode to a slice of the types or a slice of pointers to the types.

    // Slice of type
    var s1 []TypeA
    if err := executeQuery(q1, &s1); err != nil {
        // handle error
    }
    
    // Slice of pointer to type
    var s2 []*TypeB
    if err := error(q2, &s2); err != nil {
       // handle error
    }
    

    I know that this is not a direct answer to the question, but this is how this scenario is typically handled.

    打赏 评论
  • douyan8267 2015-04-02 01:50

    I don't believe this is easy to do. In the Raw Message Example in the godocs they use a value within the json, "Space" in their example, to determine which struct type to unmarshal into.

    For this to work, the function would have to have some way of getting every struct that has been defined ever for the program, and then it would have to examine each json object and compare it to each struct using reflection to figure out which type it most likely is. And what if there are multiple structs that "could be it"? Then conflict resolution complicates things.

    In short, I don't think you can do this.

    打赏 评论

相关推荐 更多相似问题