如何使用sqlx扫描嵌套结构体?

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

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

如果你想根据原始结构体标签将streetcity放入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:&quot;id&quot; db:&quot;id&quot;`
   Name   string `json:&quot;name&quot; db:&quot;name&quot;`
   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:

{
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;foo&quot;,
    &quot;street&quot;: &quot;bar&quot;,
    &quot;city&quot;: &quot;baz&quot;
}

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:&quot;id&quot; db:&quot;id&quot;`
       Name    string  `json:&quot;name&quot; db:&quot;name&quot;`
       Address Address `json:&quot;adress&quot;`
    }
    
    type Address struct {
       Street string `json:&quot;street&quot; db:&quot;street&quot;`
       City   string `json:&quot;city&quot; db:&quot;city&quot;`
    }
    
    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:&quot;blog_id&quot;`
        Title  string `db:&quot;blog_title&quot;`
        Posts  []Post
        Author Author
}
type Post struct {
        Id   int    `db:&quot;posts_id&quot;`
        Name string `db:&quot;posts_name&quot;`
}
type Author struct {
        Id       int    `db:&quot;author_id&quot;`
        Username string `db:&quot;author_username&quot;`
}

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, &amp;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:
[{
	&quot;blog_id&quot;: 1,
	&quot;blog_title&quot;: &quot;Foo&quot;,
	&quot;author&quot;: {
		&quot;author_id&quot;: 1,
		&quot;author_username&quot;: &quot;John&quot;
	},
	&quot;posts&quot;: [{
			&quot;post_id&quot;: 1,
			&quot;posts_name&quot;: &quot;Bar&quot;
		}, {
			&quot;post_id&quot;: 2,
			&quot;posts_name&quot;: &quot;Baz&quot;
		}]
}, {
	&quot;blog_id&quot;: 2,
	&quot;blog_title&quot;: &quot;Egg&quot;,
	&quot;author&quot;: {
		&quot;author_id&quot;: 2,
		&quot;author_username&quot;: &quot;Ed&quot;
	},
	&quot;posts&quot;: [{
			&quot;post_id&quot;: 3,
			&quot;posts_name&quot;: &quot;Beacon&quot;
		}]
}]

huangapple
  • 本文由 发表于 2021年6月21日 18:34:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/68066360.html
匿名

发表评论

匿名网友

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

确定