Golang抽象的接口切片转换

huangapple go评论72阅读模式
英文:

Golang abstracted Interface Slice conversion

问题

我有一个对象列表(具体来说是olievere/Elastic SearchResult.Hits)。每个对象都有一个json.RawMessage对象,我想创建一个可抽象化的方法,该方法接受任何结构的接口切片,将每个单独的hits的json.RawMessage解析为该结构,并将其附加到传入的[]interface中。

这个函数不应该有任何逻辑或对所需业务层结构的了解,数据库调用的接口化程度很高,因此对上述提到的Elastic包没有可见性。以下是我尝试做的示例...

foo.go

import (
    bar
    package
)

type TestStruct struct {
    Slice []*package.Struct // package.Struct具有一个名为Source的值,它是一个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))
    }
}

第二个文件

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

我希望将类型为[]OtherType或我决定创建的任何其他新结构的内容交给UnmarshalStruct,并让它返回给我相同的结构,只是数据已经填充。

举个例子,我将从Elastic中搜索两种不同类型的数据。我将收到以下两个对象列表之一。

{
    "foo": "",
    "id": ""
}

在不同的索引中

{
    "bar": "",
    "baz": "",
    "eee": ""
}

每个对象都需要两个不同的结构。

然而,我希望有一个单一的方法能够解码这两个列表。我将提供以下内容,并使用相同的函数将其转换为bar结构和另一种类型的foo结构。

{
    "source": [
        {
            "bar": "",
            "baz": "",
            "eee": ""
        },
        {
            "bar": "",
            "baz": "",
            "eee": ""
        },
        {
            "bar": "",
            "baz": "",
            "eee": ""
        }
    ]
}
英文:

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': ''
    }    
  ]
}

答案1

得分: 3

这是你要翻译的内容:

没有办法在不使用反射的情况下实现你想要的功能。我个人会以不同的结构来组织代码,将其反序列化为更通用的类型,比如map[string]string,或者像@ThunderCat所示,摒弃中间状态,直接反序列化为正确的类型。但是这是可行的...

(我将json.RawMessage直接移入TestStruct中,以消除一级间接性并使示例更清晰)

type TestStruct struct {
    Slice []json.RawMessage
}

func (t TestStruct) UnmarshalStruct(v interface{}) error {
    // 获取底层切片的Value
    slice := reflect.ValueOf(v).Elem()
    // 确保我们有足够的容量
    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
}

然后可以这样调用它:

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

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

英文:

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

答案2

得分: 1

如果我理解正确,您想要将数据反序列化为两种类型的切片:

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"`
}

SearchHit Source 中获取数据。

JSON 包可以为您完成大部分工作:

func executeQuery(q Query, v interface{}) error {
   // 获取一个 SearchHit。这只是一个示例,我不知道该包的具体工作方式。
   searchHit, err := getHit(q) 
   if err != nil {
      return err
   }
   // 这是重要的部分。将原始消息转换为字节切片,并解码为调用者的切片。
   return json.Unmarshal([]byte(*searchHit.Source), v)
}

您可以调用此函数来解码为类型的切片或类型指针的切片。

// 类型的切片
var s1 []TypeA
if err := executeQuery(q1, &s1); err != nil {
    // 处理错误
}

// 类型指针的切片
var s2 []*TypeB
if err := executeQuery(q2, &s2); err != nil {
   // 处理错误
}

我知道这不是直接回答您的问题,但这通常是处理这种情况的方式。

英文:

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.

答案3

得分: 0

我不认为这是容易做到的。在godocs的原始消息示例中,他们使用json中的一个值,例如"Space",来确定要解组成的结构类型。

要实现这个功能,函数必须有一种方式来获取程序中定义的每个结构体,然后它必须使用反射来检查每个json对象并将其与每个结构体进行比较,以确定它最可能是哪种类型。如果有多个结构体"可能是它"怎么办?那么冲突解决会使事情变得复杂。

简而言之,我认为你不能这样做。

英文:

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.

1: http://golang.org/pkg/encoding/json/#RawMessage "Raw Message Example"

huangapple
  • 本文由 发表于 2015年4月2日 04:23:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/29400044.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定