反序列化嵌套的 JSON,或者在 Go 中将其传递给下一个步骤。

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

Deserializing nested JSON, or simply pass it forward in Go

问题

使用Go构建基本的API时,我在postgres表中的一个JSON字段中存储了JSON数据,还有一些其他的(普通)数据类型。使用我的模型,我只是尝试从数据库中获取一行数据,并将其作为JSON传递。

使用GORM将数据反序列化为结构体时,大部分映射都可以无缝进行,只有JSON字段,根据选择的数据类型,可能会呈现为字节数组或字符串。

以下是更新后的模型:

type Item struct {
    // --snip--
    Stats []ItemInfo `gorm:"column:stats" json:"stats" sql:"json"`
    // --snip--
}

type ItemInfo struct {
    Stat   string `json:"stat"`
    Amount int    `json:"amount"`
}

典型的JSON数据如下(来自数据库):

[
    {"stat": "Multistrike", "amount": 193},
    {"stat": "Crit", "amount": 145},
    {"stat": "Agility", "amount": 254},
    {"stat": "Stamina", "amount": 381}
]

所以我的想法是只是简单地传递这些数据,不修改它,也不将其反序列化为Go结构体或其他任何形式。控制器/路由如下:

func GetItem(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))

    // 将参数转换为int,用于数据库查询
    if err != nil {
        panic(err)
    }

    // 获取数据库上下文
    db, ok := c.MustGet("databaseConnection").(gorm.DB)
    if !ok {
        // 处理错误
    }

    // 在这里保存结构化的item
    var returnedItem models.Item

    // 获取数据库行
    db.Where(&models.Item{ItemID: id}).First(&returnedItem)

    if c.Bind(&returnedItem) == nil {
        // 以JSON形式响应结构体
        c.JSON(http.StatusOK, returnedItem)
    }
}

这将以以下JSON形式响应(其中stats为json.RawMessage):

{
    "context": "raid-finder",
    "stats": "W3sic3RhdCI6ICJWZXJzYXRpbGl0eSIsICJhbW91bnQiOiA0NX0sIHsic3RhdCI6ICJDcml0IiwgImFtb3VudCI6IDEwMH0sIHsic3RhdCI6ICJBZ2lsaXR5IiwgImFtb3VudCI6IDEwOX0sIHsic3RhdCI6ICJTdGFtaW5hIiwgImFtb3VudCI6IDE2M31d"
}

或者(其中stats为字符串):

{
    "context": "raid-finder",
    "stats": "[{\"stat\": \"Versatility\", \"amount\": 45}, {\"stat\": \"Crit\", \"amount\": 100}, {\"stat\": \"Agility\", \"amount\": 109}, {\"stat\": \"Stamina\", \"amount\": 163}]"
}

我有哪些选项可以简单地传递这些数据?到目前为止,我尝试将JSON映射到结构体(由于动态数据和我选择JSON的原因,这变得困难),但没有成功。

我意识到从gin-gonic中有一些魔法,c.JSON自动(?)将所有数据从结构体编组为JSON,但希望有一种方法可以避免编组JSON数据?

当使用ItemInfo子结构运行时,它会出现以下错误:

2016/01/07 08:21:08 Panic recovery -> reflect.Set: value of type []uint8 is not assignable to type []models.ItemInfo
/usr/local/go/src/runtime/panic.go:423 (0x42a929)
        gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
/usr/local/go/src/reflect/value.go:2158 (0x5492ce)
        Value.assignTo: panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String())
/usr/local/go/src/reflect/value.go:1327 (0x546195)

编辑:更新的代码:

英文:

Building a basic API with Go, I've got JSON stored in a JSON field in a postgres table, along with some other (plain) datatypes. Using my model, I'm simply trying to fetch a row from the database and pass it forward as JSON.

Using GORM to deserialize the data into a struct, most of the mapping happens seamlessly, except for the JSON, which depending on selected datatype either renders as a bytearray or string.

Here are the models (Updated):

type Item struct {
    --snip--
    Stats []ItemInfo `gorm:"column:stats" json:"stats" sql:"json"`
    --snip--
}

type ItemInfo struct {
    Stat        string      `json:"stat"`
    Amount      int         `json:"amount"`
}

With the typical JSON looking like this (from the DB):

[{"stat": "Multistrike", "amount": 193}, {"stat": "Crit", "amount": 145}, 
 {"stat": "Agility", "amount": 254}, {"stat": "Stamina", "amount": 381}]

So the idea is that I simply want to pass this data on, not alter it, or deserialize it to a Go struct or anything. The controller/route follows:

func GetItem(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    
    // Convert Parameter to int, for db query
    if err != nil {
        panic(err)
    }
    
    // Get the DB context
    db, ok := c.MustGet("databaseConnection").(gorm.DB)
    if !ok {
        // Do something
    }
    
    // Hold the structified item here.
    var returnedItem models.Item
    
    // Get the db row
    db.Where(&models.Item{ItemID: id}).First(&returnedItem)
    
    if c.Bind(&returnedItem) == nil {
        
        // Respond with the struct as json
        c.JSON(http.StatusOK, returnedItem)
    }
}

Which responds with the following JSON (with stats as json.RawMessage):

{

    "context": "raid-finder",
    "stats": "W3sic3RhdCI6ICJWZXJzYXRpbGl0eSIsICJhbW91bnQiOiA0NX0sIHsic3RhdCI6ICJDcml0IiwgImFtb3VudCI6IDEwMH0sIHsic3RhdCI6ICJBZ2lsaXR5IiwgImFtb3VudCI6IDEwOX0sIHsic3RhdCI6ICJTdGFtaW5hIiwgImFtb3VudCI6IDE2M31d",
}

Or alternatively (with stats as string):

{
    "context": "raid-finder",
    "stats": "[{\"stat\": \"Versatility\", \"amount\": 45}, {\"stat\": \"Crit\", \"amount\": 100}, {\"stat\": \"Agility\", \"amount\": 109}, {\"stat\": \"Stamina\", \"amount\": 163}]",
}

What options do I have to simply pass this on, so far I've unsuccessfully attempted to map the JSON to a struct (which becomes difficult because of the dynamic data, and the reason I chose JSON to start with)?

I realize there's some magic going on from gin-gonic, with c.JSON automatically(?) marshalling all of the data from the struct to JSON, but hoping there's some way to avoid marshalling the json data?

When ran with the ItemInfo substruct, it panics with the following error:

2016/01/07 08:21:08 Panic recovery -> reflect.Set: value of type []uint8 is not assignable to type []models.ItemInfo
/usr/local/go/src/runtime/panic.go:423 (0x42a929)
        gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
/usr/local/go/src/reflect/value.go:2158 (0x5492ce)
        Value.assignTo: panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String())
/usr/local/go/src/reflect/value.go:1327 (0x546195)

EDIT: Updated code:

答案1

得分: 0

创建一个名为itemInfo的子结构体,类似于以下代码:

type itemInfo struct {
    Stat string `json:"stat"`
    Crit int    `json:"crit"`
}

然后在你的Item结构体中添加:

type Item struct {
    --snip--
    Context string     `gorm:"column:context" json:"context"`
    Stats   []itemInfo `gorm:"column:stats" json:"stats" sql:"json"`
    --snip--
}

当你进行解组(unmarshal)操作时,它应该可以正常地转换为itemInfo

另外,我假设你正在使用暴雪的 API,我已经创建了一个包装器,你可以在这里查看:https://github.com/Gacnt/Go-WoW-API,了解我是如何实现的。但是请注意,这个包装器是未完成的,我只实现了我在开发时所需的部分功能。

英文:

Make a sub struct like itemInfo or something like:

type itemInfo struct {
    Stat string `json:"stat"`
    Crit int    `json:"crit"`
}

Then in your Item struct make

type Item struct {
    --snip--
    Context string `gorm:"column:context" json:"context"`
    Stats []itemInfo `gorm:"column:stats" json:"stats" sql:"json"`
    --snip--
}

Then when you unmarshal it should go just fine into item info!

Also I'm assuming you're using blizzards API, I have made a wrapper already you can view it here: https://github.com/Gacnt/Go-WoW-API to see how I have done it, but it's completely unfinished I only implemented the parts I needed when I was working on something.

答案2

得分: 0

原来最简单的方法是在Item结构体中提供一个额外的属性,供GORM解组为[]byte,然后将字节数组解组为子结构体:

// Item是一个东西..
type Item struct {
    Stats     []byte     `gorm:"column:stats" json:"stats"`
    StatsList []ItemInfo `json:"iteminfo"`
}

然后像这样解组它:

err = json.Unmarshal(returnedItem.Stats, &returnedItem.StatsList)

感谢@evanmcdonnal的建议。

英文:

Turns out it was easiest to just provide an additional property in the Item struct for GORM to unmarshal into []byte and then unmarshal the byte array as the sub-struct:

// Item is a thing..
type Item 
    Stats           []byte    `gorm:"column:stats"  json:"stats"`
    StatsList       []ItemInfo `json:"iteminfo"`
}

And unmarshal it like this:

err = json.Unmarshal(returnedItem.Stats, &returnedItem.StatsList)

Thanks to @evanmcdonnal for the suggestion.

huangapple
  • 本文由 发表于 2016年1月6日 03:19:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/34619613.html
匿名

发表评论

匿名网友

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

确定