将ObjectId替换为嵌入的JSON在编组结构时。

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

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&#39;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"
  }
}

Playground

英文:

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&#39;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:&quot;id,omitempty&quot;`
	Title string        `json:&quot;title,omitempty&quot;`
	Owner ownerObjID    `json:&quot;owner,omitempty&quot;` // &lt;-- is this type wrapping doable/easy with MongoDB?
}

type User struct {
	ID   bson.ObjectId `json:&quot;id,omitempty&quot;`
	Name string        `json:&quot;name,omitempty&quot;`
}

func main() {
	post := LookupPost()
	b, err := json.MarshalIndent(post, &quot;&quot;, &quot;  &quot;)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf(&quot;JSON:\n%s\n&quot;, b)
}

// Some stubs for demo:
func LookupPost() Post {
	return Post{
		ID:    &quot;postID001&quot;,
		Title: &quot;Ima Test&quot;,
		Owner: ownerObjID{&quot;ownerID002&quot;},
	}
}

func LookupUser(id bson.ObjectId) (User, error) {
	return User{
		ID:   id,
		Name: &quot;name for &quot; + string(id),
	}, nil
}

<kbd>Playground</kbd>

Gives me:

JSON:
{
  &quot;id&quot;: &quot;postID001&quot;,
  &quot;title&quot;: &quot;Ima Test&quot;,
  &quot;owner&quot;: {
    &quot;id&quot;: &quot;ownerID002&quot;,
    &quot;name&quot;: &quot;name for ownerID002&quot;
  }
}

答案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:&quot;_id,omitempty&quot; json:&quot;id,omitempty&quot;`
	Title string `bson:&quot;title,omitempty&quot; json:&quot;title,omitempty&quot;`
	Owner UserRef `bson:&quot;owner,omitempty&quot; json:&quot;owner,omitempty&quot;`
}

type User struct {
    ID   bson.ObjectId `json:&quot;id,omitempty&quot;`
    Name string        `json:&quot;name,omitempty&quot;`
}

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.

huangapple
  • 本文由 发表于 2015年6月3日 12:50:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/30611086.html
匿名

发表评论

匿名网友

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

确定