0

In the following example from Web Development with Go by Shiju Varghese, which is for implementing a HTTP server using a new MongoDB session for each HTTP request:

  • Why is json package's Decode method used in PostCategory function?

  • Why is json package's Marshal method used in GetCategories function?

At first I thought that Decode in PostCategory and Marshal in GetCategories are opposite to each other, but later I found that there is a Unmarshal method and maybe a Encode one in the json package. So I asked a question earlier.

Here is the program

package main
import (
    "encoding/json"
    "log"
    "net/http"
    "github.com/gorilla/mux"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
)
var session *mgo.Session

type (
    Category struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Description string
    }
    DataStore struct {
session *mgo.Session
    }
)
//Close mgo.Session
func (d *DataStore) Close() {
    d.session.Close()
}
//Returns a collection from the database.
func (d *DataStore) C(name string) *mgo.Collection {
    return d.session.DB("taskdb").C(name)
}
//Create a new DataStore object for each HTTP request
func NewDataStore() *DataStore {
    ds := &DataStore{
session: session.Copy(),
    }
    return ds
}

//Insert a record
func PostCategory(w http.ResponseWriter, r *http.Request) {
    var category Category
    // Decode the incoming Category json
    err := json.NewDecoder(r.Body).Decode(&category)
    if err != nil {
panic(err)
    }
    ds := NewDataStore()
    defer ds.Close()
    //Getting the mgo.Collection
    c := ds.C("categories")
    //Insert record
    err = c.Insert(&category)
    if err != nil {
panic(err)
    }
w.WriteHeader(http.StatusCreated)
}

//Read all records
func GetCategories(w http.ResponseWriter, r *http.Request) {
    var categories []Category
    ds := NewDataStore()
    defer ds.Close()
    //Getting the mgo.Collection
    c := ds.C("categories")
    iter := c.Find(nil).Iter()
    result := Category{}
    for iter.Next(&result) {
categories = append(categories, result)
    }
w.Header().Set("Content-Type", "application/json")
j, err := json.Marshal(categories)
if err != nil {
panic(err)
    }
w.WriteHeader(http.StatusOK)
w.Write(j)
}

func main() {
    var err error
    session, err = mgo.Dial("localhost")
    if err != nil {
panic(err)
    }
    r := mux.NewRouter()
r.HandleFunc("/api/categories", GetCategories).Methods("GET")
r.HandleFunc("/api/categories", PostCategory).Methods("POST")
    server := &http.Server{
Addr:    ":8080",
Handler: r,
    }
    log.Println("Listening...")
    server.ListenAndServe()
}
Community
  • 1
  • 1
Tim
  • 1
  • 141
  • 372
  • 590

2 Answers2

4

I think the main reason for using a json.NewDecoder here is to read directly from response's body (r.Body) here, since NewDecoder takes an io.Reader as an input.

You could have used json.Unmarshal but then you'd have to first read response body into a []byte and pass that value to json.Unmarshal. NewDecoder is more convenient here.

abhink
  • 8,740
  • 1
  • 36
  • 48
  • Thanks. Do you mean that `Unmarshal` and `Decode` can apply to the same object, and the only difference is that the object should be treated as a byte array for `Unmarshal` while the object should be treated as an `io.Reader` object? – Tim Jul 27 '16 at 18:18
  • That is basically the idea. In fact, if you look at `Decode`'s source, you'll see that it also uses `Unmarshal` internally (although defined as a private package member. (https://github.com/golang/go/blob/master/src/encoding/json/stream.go#L67) – abhink Jul 27 '16 at 18:35
  • Also, `Decode` is very useful when you have a type implementing the `json.Unmarshaler` interface, as it can be used to decode nested structures. e.g. http://stackoverflow.com/questions/20587157/golang-having-trouble-with-nested-json-unmarshaler – abhink Jul 27 '16 at 18:38
1

TL;DR — Marshal/Unmarshal take and return byte slices, while Encode/Decode do the same thing, but read the bytes from a stream such as a network connection (readers and writers).

The encoding/json package uses the Encoder and Decoder types to act on streams of data, that is, io.Reader's and io.Writer's. This means that you can take data directly from a network socket (or an HTTP body in this case which implements io.Reader) and transform it to JSON as the bytes come in. Doing it this way, we can go ahead and start processing that JSON as soon as any data is available but before we've received the whole document (on a slow network connection with a big document this could save us a lot of time, and for some streaming protocols with "infinitely sized" document streams this is absolutely necessary!)

Marshal and Unmarshal however operate on byte slices, which means that you have to have the entirety of the JSON document in memory before you can use them. In your example, the author uses Marshal because they already have a []byte slice so there's no point in constructing a buffer using the byte slice, then making an encoder that uses that buffer, then calling encode: Instead they can just let Marshal do that for them.

In reality, Marshal/Unmarshal are just convenience methods on top of Encoders and Decoders. If we look at the source for Unmarshal, we see that under the hood it's just constructing an encoder (or the internal representation of an encoder, but trust me, they're the same thing, if you want proof you can look at the Encode method source and see that it's also creating an encodeState) and then returning the output bytes:

func Marshal(v interface{}) ([]byte, error) {
    e := &encodeState{}
    err := e.marshal(v)
    if err != nil {
        return nil, err
    }
    return e.Bytes(), nil
}
Sam Whited
  • 6,880
  • 2
  • 31
  • 37