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
}
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"
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With