英文:
Replacing ObjectId with embedded json when marshaling struct
问题
我正在使用Go和MongoDB构建一个RESTful API,并且在将一个文档的JSON嵌入到另一个文档的JSON中遇到了一些困难。以下是我尝试实现的一个示例。我有以下的模式:
type Post struct {
ID bson.ObjectId `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Owner bson.ObjectId `json:"owner,omitempty"` // 引用一个User
}
type User struct {
ID bson.ObjectId `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
在创建一个帖子的JSON时,我想首先在MongoDB中查找帖子的所有者,并将结果用户嵌入到该帖子的JSON中(替换原始的ObjectId
),如下所示:
{
"id": "...",
"title": "我的精彩帖子",
"owner": {
"id": "...",
"name": "Cody"
}
}
除了手动使用map[string]interface{}
构建JSON之外,我不太确定如何实现这一点,像这样:
post := LookupPost(...)
user := LookupUser(post.Owner)
m := map[string]interface{}{
"id": post.ID,
"title": post.Title,
"owner": map[string]interface{}{
"id": user.ID,
"name": user.Name,
},
}
b, _ := json.Marshal(m)
显然,这种方法不太适用于大规模应用,也不够DRY(Don't Repeat Yourself)-理想情况下,我希望能够利用每个结构定义中的json
标签,并自动插入字段。
我是否遗漏了什么,或者我尝试做的事情是不可能的?或者我是否没有正确地处理Go中的MongoDB/JSON?从我的角度来看,我来自Node.js背景,这种功能是微不足道的。
编辑
为了澄清问题,以下是一些不正确的Go代码,展示了我想要做的事情:
func getPostJSON() []byte {
p := LookupPost(...)
u := LookupUser(p.Owner, ...)
uj, _ := json.Marshal(u)
p.Owner = uj // 无法在Go中这样做
pj, _ := json.Marshal(p)
return pj
}
英文:
I'm building a RESTful API with Go and MongoDB, and I'm running into some difficulty with embedding the JSON for one document inside the JSON for another. Here's a toy example of what I'm trying to accomplish. I have the following schemas:
type Post struct {
ID bson.ObjectId `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Owner bson.ObjectId `json:"owner,omitempty"` // references a User
}
type User struct {
ID bson.ObjectId `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
When creating the JSON for a post, I'd like to first look up the owner of the post in MongoDB and embed the resulting user inside said post's JSON (in-place of the original ObjectId
), like so:
{
"id": "...",
"title": "My awesome post",
"owner": {
"id": "...",
"name": "Cody"
}
}
I'm not quite sure how to accomplish this, other than manually constructing the JSON using map[string]interface{}
, like so:
post := LookupPost(...)
user := LookupUser(post.Owner)
m := map[string]interface{}{
"id": post.ID,
"title": post.Title,
"owner": map[string]interface{}{
"id": user.ID,
"name": user.Name,
},
}
b, _ := json.Marshal(m)
Obviously this <strike>doesn't scale very well</strike> isn't very DRY -- ideally, I'd be able to utilize the json
tags in each struct definition and have the fields inserted automatically.
Am I missing something, or is what I'm trying to do impossible? Or am I simply not approaching MongoDB/JSON in Go correctly? To put things in perspective, I'm coming from a Node.js background, where this sort of functionality is trivial.
Edit
To clarify things, here's some incorrect Go code that shows what I'd like to do
func getPostJSON() []byte {
p := LookupPost(...)
u := LookupUser(p.Owner, ...)
uj, _ := json.Marshal(u)
p.Owner = uj // can't do this in Go
pj, _ := json.Marshal(p)
return pj
}
答案1
得分: 1
我不熟悉MongoDB或bson.ObjectId
,但是你可以为你的User
字段替换自己的类型,并且MongoDB可以轻松地从用户的bson.ObjectId
中填充它吗?
如果可以的话,你可以将用户对象ID包装到实现了json.Marshaler
接口的自定义类型中。例如:
// 嵌入(而不是`type x bson.ObjectId`),以便我们获得所有方法并满足所有接口,这样希望MongoDB可以从数据库中填充此类型的字段?
type ownerObjID struct{ bson.ObjectId }
// 这里我们将查找用户的结果编组为用户对象,而不仅仅是ID本身。
func (oid ownerObjID) MarshalJSON() ([]byte, error) {
user, err := LookupUser(oid.ObjectId)
if err != nil {
return nil, err
}
return json.Marshal(user)
}
type Post struct {
ID bson.ObjectId `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Owner ownerObjID `json:"owner,omitempty"` // <-- 这种类型的包装在MongoDB中是否可行/容易?
}
type User struct {
ID bson.ObjectId `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
func main() {
post := LookupPost()
b, err := json.MarshalIndent(post, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf("JSON:\n%s\n", b)
}
// 一些演示的存根:
func LookupPost() Post {
return Post{
ID: "postID001",
Title: "Ima Test",
Owner: ownerObjID{"ownerID002"},
}
}
func LookupUser(id bson.ObjectId) (User, error) {
return User{
ID: id,
Name: "name for " + string(id),
}, nil
}
给我:
JSON:
{
"id": "postID001",
"title": "Ima Test",
"owner": {
"id": "ownerID002",
"name": "name for ownerID002"
}
}
英文:
I'm not familar with MongoDB or bson.ObjectId
, but can you substitute your own type for your User
field and have MongoDB easily fill that in for you from a user's bson.ObjectId
?
If so you can just wrap user object id's into their own type that implements the json.Marshaler
interface. E.g.:
// Embedded (instead of `type x bson.ObjectId`) so that we
// get all the methods and satisfy all the interfaces that
// bson.ObjectId does. Hopefully that's engough to allow MongoDB
// to fill in fields of this type from a database??
type ownerObjID struct{ bson.ObjectId }
// Here we marshal the results of looking up the user from the id
// rather than just the ID itself.
func (oid ownerObjID) MarshalJSON() ([]byte, error) {
user, err := LookupUser(oid.ObjectId)
if err != nil {
return nil, err
}
return json.Marshal(user)
}
type Post struct {
ID bson.ObjectId `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Owner ownerObjID `json:"owner,omitempty"` // <-- is this type wrapping doable/easy with MongoDB?
}
type User struct {
ID bson.ObjectId `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
func main() {
post := LookupPost()
b, err := json.MarshalIndent(post, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf("JSON:\n%s\n", b)
}
// Some stubs for demo:
func LookupPost() Post {
return Post{
ID: "postID001",
Title: "Ima Test",
Owner: ownerObjID{"ownerID002"},
}
}
func LookupUser(id bson.ObjectId) (User, error) {
return User{
ID: id,
Name: "name for " + string(id),
}, nil
}
Gives me:
JSON:
{
"id": "postID001",
"title": "Ima Test",
"owner": {
"id": "ownerID002",
"name": "name for ownerID002"
}
}
答案2
得分: 1
所以,我实际上找到了一个更简洁的解决方案来解决这个问题:
type Post struct {
ID bson.ObjectId `bson:"_id,omitempty" json:"id,omitempty"`
Title string `bson:"title,omitempty" json:"title,omitempty"`
Owner UserRef `bson:"owner,omitempty" json:"owner,omitempty"`
}
type User struct {
ID bson.ObjectId `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type UserRef bson.ObjectId
func (ref UserRef) GetBSON() (interface{}, error) {
return bson.ObjectId(ref), nil
}
func (ref UserRef) MarshalJSON() ([]byte, error) {
u := LookupUserInMongoDB(ref)
return json.Marshal(u)
}
这是它的工作原理——当将Post转换为bson时,mgo无法将UserRef存储为ObjectId,因此我们可以为UserRef实现GetBSON
方法,以返回底层的ObjectId。这样我们就可以在数据库中将Owner存储为ObjectId。并且,就像@DaveC的答案中一样,我们为UserRef实现MarshalJSON
方法,这样在将Post转换为json时,我们可以用实际嵌入的用户替换ObjectId。
英文:
So I actually discovered a much cleaner solution to this problem:
type Post struct {
ID bson.ObjectId `bson:"_id,omitempty" json:"id,omitempty"`
Title string `bson:"title,omitempty" json:"title,omitempty"`
Owner UserRef `bson:"owner,omitempty" json:"owner,omitempty"`
}
type User struct {
ID bson.ObjectId `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type UserRef bson.ObjectId
func (ref UserRef) GetBSON() (interface{}, error) {
return bson.ObjectId(ref), nil
}
func (ref UserRef) MarshalJSON() ([]byte, error) {
u := LookupUserInMongoDB(ref)
return json.Marshal(u)
}
Here's how it works -- mgo can't store UserRef as an ObjectId when converting a Post to bson, so we can implement the GetBSON
method for UserRef to return the underlying ObjectId. This allows us to store Owner as an ObjectId in the database. And, like in @DaveC's answer, we implement the MarshalJSON
method for UserRef so that when converting a Post to json, we can replace the ObjectId with an actual embedded user.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论