英文:
How to scan into nested structs with sqlx?
问题
假设我有两个模型:
type Customer struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Address Address `json:"address"`
}
type Address struct {
Street string `json:"street" db:"street"`
City string `json:"city" db:"city"`
}
// ...
customer := models.Customer{}
err := db.Get(&customer, `select * from users where id=$1 and name=$2`, id, name)
但是这个扫描操作会抛出一个错误:missing destination name street in *models.Customer
。
我做错了什么吗?正如你所看到的,我已经更新了与值对应的数据库。我仔细检查过,所以大小写敏感应该不是问题。
或者使用 https://github.com/jmoiron/sqlx 是不可能的吗?
我在文档中看到了这个问题,但仍然无法解决。
http://jmoiron.github.io/sqlx/#advancedScanning
users
表的声明如下:
CREATE TABLE `users` (
`id` varchar(256) NOT NULL,
`name` varchar(150) NOT NULL,
`street` varchar(150) NOT NULL,
`city` varchar(150) NOT NULL,
)
英文:
Let's assume that I have two models,
type Customer struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Address Address `json:"adress"`
}
type Address struct {
Street string `json:"street" db:"street"`
City string `json:"city" db:"city"`
}
// ...
customer := models.Customer{}
err := db.Get(&customer , `select * from users where id=$1 and name=$2`, id, name)
But this scan throws an error as: missing destination name street in *models.Customer
Am I doing something wrong? As you can see I already updated the db corresponding of the value. I doubled check so case sensitivity shouldn't be a problem.
Or is it not possible using https://github.com/jmoiron/sqlx?
I can see it in the documentation but still couldn't figure out how to solve it.
http://jmoiron.github.io/sqlx/#advancedScanning
The users
table is declared as:
CREATE TABLE `users` (
`id` varchar(256) NOT NULL,
`name` varchar(150) NOT NULL,
`street` varchar(150) NOT NULL,
`city` varchar(150) NOT NULL,
)
答案1
得分: 8
你发布的链接给出了如何实现这个的提示:
> StructScan非常复杂。它支持嵌入结构,并且使用与Go语言用于嵌入属性和方法访问的相同优先级规则来赋值给字段。
因此,根据你的数据库模式,你可以将Address
嵌入到Customer
中:
type Customer struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Address
}
在你的原始代码中,Address
是一个带有自己的db
标签的字段<strike>。这是不正确的,而且顺便说一下,你的模式根本没有address
列。(看起来你已经从代码片段中删除了它)
通过将结构体嵌入到Customer
中,Address
字段(包括标签)将被提升到Customer
中,sqlx
将能够从查询结果中填充它们。
警告:嵌入字段还会扁平化任何JSON编组的输出。它将变成:
{
"id": 1,
"name": "foo",
"street": "bar",
"city": "baz"
}
如果你想根据原始结构体标签将street
和city
放入JSON的address
对象中,最简单的方法可能是将数据库结构体重新映射到原始类型。
你也可以将查询结果扫描到map[string]interface{}
中,但是你必须小心处理Postgres数据类型在Go中的表示方式。
英文:
The very link you posted gives you an hint about how to do this:
> StructScan is deceptively sophisticated. It supports embedded structs, and assigns to fields using the same precedence rules that Go uses for embedded attribute and method access
So given your DB schema, you can simply embed Address
into Customer
:
type Customer struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Address
}
In your original code, Address
was a field <strike>with its own db
tag. This is not correct, and by the way your schema has no address
column at all.</strike> (it appears you edited it out of your code snippet)
By embedding the struct into Customer
instead, Address
fields including tags are promoted into Customer
and sqlx
will be able to populate them from your query result.
Warning: embedding the field will also flatten the output of any JSON marshalling. It will become:
{
"id": 1,
"name": "foo",
"street": "bar",
"city": "baz"
}
If you want to place street
and city
into a JSON address
object as based on your original struct tags, the easiest way is probably to remap the DB struct to your original type.
You could also scan the query result into a map[string]interface{}
but then you have to be careful about how Postgres data types are represented in Go.
答案2
得分: 1
我遇到了同样的问题,并想出了一个比@blackgreen的解决方案更加优雅的解决方案。
他是对的,最简单的方法是嵌入对象,但我是在一个临时对象中进行操作,而不是让原始对象变得更加混乱。
然后,你可以添加一个函数,将你的临时(扁平化)对象转换为真实的(嵌套的)对象。
type Customer struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Address Address `json:"address"`
}
type Address struct {
Street string `json:"street" db:"street"`
City string `json:"city" db:"city"`
}
type tempCustomer struct {
Customer
Address
}
func (c *tempCustomer) ToCustomer() Customer {
customer := c.Customer
customer.Address = c.Address
return customer
}
现在,你可以将数据扫描到tempCustomer中,然后在返回之前简单地调用tempCustomer.ToCustomer。这样可以保持你的JSON干净,并且不需要自定义扫描函数。
英文:
I had the same problem and came up with a slightly more elegant solution than @blackgreen's.
He's right, the easiest way is to embed the objects, but I do it in a temporary object instead of making the original messier.
You then add a function to convert your temp (flat) object into your real (nested) one.
type Customer struct {
Id int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Address Address `json:"adress"`
}
type Address struct {
Street string `json:"street" db:"street"`
City string `json:"city" db:"city"`
}
type tempCustomer struct {
Customer
Address
}
func (c *tempCustomer) ToCustomer() Customer {
customer := c.Customer
customer.Address = c.Address
return customer
}
Now you can scan into tempCustomer and simply call tempCustomer.ToCustomer before you return. This keeps your JSON clean and doesn't require a custom scan function.
答案3
得分: 0
使用Carta这个轻量级库可以帮助你:
样例模式:
type Blog struct {
Id int `db:"blog_id"`
Title string `db:"blog_title"`
Posts []Post
Author Author
}
type Post struct {
Id int `db:"posts_id"`
Name string `db:"posts_name"`
}
type Author struct {
Id int `db:"author_id"`
Username string `db:"author_username"`
}
查询语句:
select
id as blog_id,
title as blog_title,
P.id as posts_id,
P.name as posts_name,
A.id as author_id,
A.username as author_username
from blog
left outer join author A on blog.author_id = A.id
left outer join post P on blog.id = P.blog_id
使用方法:
// 1) 执行查询
if rows, err = sqlDB.Query(blogQuery); err != nil {
// 错误处理
}
// 2) 实例化一个你想要填充的切片(或结构体),这里是一个示例
blogs := []Blog{}
// 3) 将 SQL 行映射到你的切片
carta.Map(rows, &blogs)
Carta 会在映射 SQL 行的同时跟踪这些关系。
SQL 结果:
rows:
blog_id | blog_title | posts_id | posts_name | author_id | author_username
1 | Foo | 1 | Bar | 1 | John
1 | Foo | 2 | Baz | 1 | John
2 | Egg | 3 | Beacon | 2 | Ed
最终结果:
blogs:
[{
"blog_id": 1,
"blog_title": "Foo",
"author": {
"author_id": 1,
"author_username": "John"
},
"posts": [{
"post_id": 1,
"posts_name": "Bar"
}, {
"post_id": 2,
"posts_name": "Baz"
}]
}, {
"blog_id": 2,
"blog_title": "Egg",
"author": {
"author_id": 2,
"author_username": "Ed"
},
"posts": [{
"post_id": 3,
"posts_name": "Beacon"
}]
}]
英文:
Using Carta a lightweight library can help:
Sample Schema:
type Blog struct {
Id int `db:"blog_id"`
Title string `db:"blog_title"`
Posts []Post
Author Author
}
type Post struct {
Id int `db:"posts_id"`
Name string `db:"posts_name"`
}
type Author struct {
Id int `db:"author_id"`
Username string `db:"author_username"`
}
Query:
select
id as blog_id,
title as blog_title,
P.id as posts_id,
P.name as posts_name,
A.id as author_id,
A.username as author_username
from blog
left outer join author A on blog.author_id = A.id
left outer join post P on blog.id = P.blog_id
Using it:
// 1) Run your query
if rows, err = sqlDB.Query(blogQuery); err != nil {
// error
}
// 2) Instantiate a slice(or struct) which you want to populate, Dummy example.
blogs := []Blog{}
// 3) Map the SQL rows to your slice
carta.Map(rows, &blogs)
Carta will map the SQL rows while keeping track of those relationships.
SQL Results:
rows:
blog_id | blog_title | posts_id | posts_name | author_id | author_username
1 | Foo | 1 | Bar | 1 | John
1 | Foo | 2 | Baz | 1 | John
2 | Egg | 3 | Beacon | 2 | Ed
Final Result:
blogs:
[{
"blog_id": 1,
"blog_title": "Foo",
"author": {
"author_id": 1,
"author_username": "John"
},
"posts": [{
"post_id": 1,
"posts_name": "Bar"
}, {
"post_id": 2,
"posts_name": "Baz"
}]
}, {
"blog_id": 2,
"blog_title": "Egg",
"author": {
"author_id": 2,
"author_username": "Ed"
},
"posts": [{
"post_id": 3,
"posts_name": "Beacon"
}]
}]
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论