Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing ObjectId with embedded json when marshaling struct

Tags:

json

mongodb

go

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 doesn't scale very well 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
}
like image 843
Cody Avatar asked Aug 23 '26 18:08

Cody


1 Answers

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
}

Playground

Gives me:

JSON:
{
  "id": "postID001",
  "title": "Ima Test",
  "owner": {
    "id": "ownerID002",
    "name": "name for ownerID002"
  }
}
like image 165
Dave C Avatar answered Aug 26 '26 22:08

Dave C



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!