Ladislav Prskavec
Ladislav Prskavec

Reputation: 4481

How do I send a JSON string in a POST request in Go

I tried working with Apiary and made a universal template to send JSON to mock server and have this code:

package main   
import (
        "encoding/json"
        "fmt"
        "github.com/jmcvetta/napping"
        "log"
        "net/http"
)
func main() {
  url := "http://restapi3.apiary.io/notes"
  fmt.Println("URL:>", url)
    
  s := napping.Session{}
  h := &http.Header{}
  h.Set("X-Custom-Header", "myvalue")
  s.Header = h    
  var jsonStr = []byte(`{ "title": "Buy cheese and bread for breakfast."}`)    
  var data map[string]json.RawMessage
  err := json.Unmarshal(jsonStr, &data)
  if err != nil {
        fmt.Println(err)
  }
    
  resp, err := s.Post(url, &data, nil, nil)
  if err != nil {
        log.Fatal(err)
  }
  fmt.Println("response Status:", resp.Status())
  fmt.Println("response Headers:", resp.HttpResponse().Header)
  fmt.Println("response Body:", resp.RawText())
}

This code doesn't send JSON properly, but I don't know why. The JSON string can be different in every call. I can't use Struct for this.

Upvotes: 403

Views: 639382

Answers (9)

OneOfOne
OneOfOne

Reputation: 99195

I'm not familiar with napping, but using Golang's net/http package works fine (playground):

func main() {
    url := "http://restapi3.apiary.io/notes"
    fmt.Println("URL:>", url)

    var jsonStr = []byte(`{"title":"Buy cheese and bread for breakfast."}`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    req.Header.Set("X-Custom-Header", "myvalue")
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    fmt.Println("response Status:", resp.Status)
    fmt.Println("response Headers:", resp.Header)
    body, _ := io.ReadAll(resp.Body)
    fmt.Println("response Body:", string(body))
}

Upvotes: 722

Isanka Wijerathne
Isanka Wijerathne

Reputation: 4146

I would use net/http package instead of the napping.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {
    url := "http://restapi3.apiary.io/notes"
    fmt.Println("URL:>", url)

    client := &http.Client{}

    var jsonStr = []byte(`
{
    "title": "Buy cheese and bread for breakfast."
}`)

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
    if err != nil {
        log.Fatal(err)
    }

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-Custom-Header", "myvalue")

    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }

    defer resp.Body.Close()

    fmt.Println("response Status:", resp.Status)
    fmt.Println("response Headers:", resp.Header)

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("response Body:", string(body))
}

This creates a new POST request with the JSON data as the request body, sets the necessary headers, and sends the request using an http.Client.

  • replace placeholders*.

Upvotes: 3

Highdeger
Highdeger

Reputation: 31

if you want to do it like that, you need to use this map for unmarshalling json string.

var data map[string]interface{}

but if you need to change the json each time and to make initialization of your requst body more convenient, you can use this map for creating json body.

var bodyJsonMap map[string]interface{}{
    "key1": val1,
    "key2": val2,
    ...
}

Then marshal it to a json-string.

Upvotes: 1

user13631587
user13631587

Reputation:

Use io.Pipe for large request bodies as mentioned in another answer. This approach avoids building the entire request body in memory by streaming the data from the JSON encoder to the network.

This answer builds on the other answer by showing how to handle errors. Always handle errors!

  • Use the pipe's CloseWithError function to propagate encoding errors back to error returned from http.Post.
  • Handle the error returned from http.Post
  • Close the response body.

Here's the code:

r, w := io.Pipe()

go func() {
    w.CloseWithError(json.NewEncoder(w).Encode(data))
}()

// Ensure that read side of pipe is closed. This
// unblocks goroutine in scenario where http.Post
// errors out before reading the entire request body.
defer r.Close()

resp, err := http.Post(url, r)
if err != nil {
    // Adjust error handling here to meet application requrirements.
    log.Fatal(err)
}
defer resp.Body.Close()
// Use the response here.

Upvotes: 8

Zombo
Zombo

Reputation: 1

If you have a lot of data to send, you can use a pipe:

package main

import (
   "encoding/json"
   "io"
   "net/http"
)

func main() {
   m := map[string]int{"SNG_ID": 75498415}
   r, w := io.Pipe()
   go func() {
      json.NewEncoder(w).Encode(m)
      w.Close()
   }()
   http.Post("https://stackoverflow.com", "application/json", r)
}

https://golang.org/pkg/io#Pipe

Upvotes: 3

mesutpiskin
mesutpiskin

Reputation: 1927

Example post request for http or https

    //Encode the data
       postBody, _ := json.Marshal(map[string]string{
          "name":  "Test",
          "email": "[email protected]",
       })
       responseBody := bytes.NewBuffer(postBody)
    //Leverage Go's HTTP Post function to make request
       resp, err := http.Post("https://postman-echo.com/post", "application/json", responseBody)
    //Handle Error
       if err != nil {
          log.Fatalf("An Error Occured %v", err)
       }
       defer resp.Body.Close()
    //Read the response body
       body, err := ioutil.ReadAll(resp.Body)
       if err != nil {
          log.Fatalln(err)
       }
       sb := string(body)
       log.Printf(sb)

Upvotes: 7

ninhjs.dev
ninhjs.dev

Reputation: 8533

If you already have a struct.

import (
    "bytes"
    "encoding/json"
    "io"
    "net/http"
    "os"
)

// .....

type Student struct {
    Name    string `json:"name"`
    Address string `json:"address"`
}

// .....

body := &Student{
    Name:    "abc",
    Address: "xyz",
}

payloadBuf := new(bytes.Buffer)
json.NewEncoder(payloadBuf).Encode(body)
req, _ := http.NewRequest("POST", url, payloadBuf)

client := &http.Client{}
res, e := client.Do(req)
if e != nil {
    return e
}

defer res.Body.Close()

fmt.Println("response Status:", res.Status)
// Print the body to the stdout
io.Copy(os.Stdout, res.Body)

Full gist.

Upvotes: 52

gaozhidf
gaozhidf

Reputation: 2789

you can just use post to post your json.

values := map[string]string{"username": username, "password": password}

jsonValue, _ := json.Marshal(values)

resp, err := http.Post(authAuthenticatorUrl, "application/json", bytes.NewBuffer(jsonValue))

Upvotes: 177

A-letubby
A-letubby

Reputation: 9122

In addition to standard net/http package, you can consider using my GoRequest which wraps around net/http and make your life easier without thinking too much about json or struct. But you can also mix and match both of them in one request! (you can see more details about it in gorequest github page)

So, in the end your code will become like follow:

func main() {
    url := "http://restapi3.apiary.io/notes"
    fmt.Println("URL:>", url)
    request := gorequest.New()
    titleList := []string{"title1", "title2", "title3"}
    for _, title := range titleList {
        resp, body, errs := request.Post(url).
            Set("X-Custom-Header", "myvalue").
            Send(`{"title":"` + title + `"}`).
            End()
        if errs != nil {
            fmt.Println(errs)
            os.Exit(1)
        }
        fmt.Println("response Status:", resp.Status)
        fmt.Println("response Headers:", resp.Header)
        fmt.Println("response Body:", body)
    }
}

This depends on how you want to achieve. I made this library because I have the same problem with you and I want code that is shorter, easy to use with json, and more maintainable in my codebase and production system.

Upvotes: 14

Related Questions