How do I send a JSON string in a POST request in Go
Asked Answered
B

9

396

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.

Bathypelagic answered 27/6, 2014 at 15:9 Comment(4)
I'm not familiar with some of the libraries you use, but as I understand it, you are trying to send a map of Jsons. Why don't you just send the string with the json?Deakin
why are you unmarshaling the json if you want to send json?Defrost
A little tip, you can create your message as a struct or map[string]interface{} to add all the values you want and then use json.Marshall to convert the map or struct to a json.Deakin
@topo, i dug into napping's source code, if payload is set, they call json.Marshall on it, I'm not sure why it wasn't working for him.Strophic
S
715

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))
}
Strophic answered 27/6, 2014 at 15:33 Comment(11)
now it have panics on playground. May be you shold fix or update something?Godinez
@Godinez it can't work on the playground, I just used it to paste the code, you can't open external connections from it.Strophic
@Godinez may this link help youDonela
@Godinez +1 for solid band name suggestion.Flamen
Just a warning, don't forget that by default the golang http client never times out, so for the real world, best to set something along the lines of client.Timeout = time.Second * 15Kisner
Can this be updated to gather / inspect all it's errors? This is (for me, at least) the top result on google for making post requests in Go, and it's a good answer, but I see a ton of example code that just ignores errors, and I think it encourages bad practice in newbies. Then again, if anyone regularly ignores errors, I suppose they'll learn why not to eventually, but why not encourage good practice to begin with?Endstopped
how can i send json inside json?Hollands
json.RawMessageStrophic
to avoid troubles with special characters it is better to use json.Marshal to create a payload instead of jsonStrSade
io.ReadAll(resp.Body) should be changed to ioutil. I get ReadAll not present. Perhaps deprecated.Parolee
The ioutil package is deprecatedStrophic
S
174

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))
Singhal answered 5/11, 2016 at 9:25 Comment(5)
I get this error : cannot use jsonValue (type []byte) as type io.Reader in argument to http.Post: []byte does not implement io.Reader (missing Read method)Sororate
@MandarVaze i think you may get wrong io.Reader for http.Post, and bytes.NewBuffer() works well in my codeSinghal
I'm on go 1.7, if it matters. The code listed by @Strophic works (which also uses bytes.NewBuffer() but uses http.NewRequest instead of http.Post)Sororate
According to golang.org/pkg/net/http/#Post, "Caller should close resp.Body when done reading from it. If the provided body is an io.Closer, it is closed after the request." How can I tell, as a Go newbie, if the body is an io.Closer, or in other words, if this example is safe?Vaal
The only limitation with this approach is that you can't set custom headers :/Catlike
B
50

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.

Banks answered 6/2, 2019 at 6:41 Comment(0)
V
14

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.

Volcanism answered 9/11, 2014 at 5:13 Comment(4)
If GoRequest wraps net/http. Is it possible to add this to disable the Insecure certificate for TLS ? tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }Livesay
@Livesay It's always a terrible idea to contribute code examples of skipping TLS verification in any scenario in any language... you accidentally perpetuate a lot copy/paste "workarounds" by neophytes who visit StackOverflow and don't understand the nature of why fixing TLS errors is crucial. Either fix your certificate import path (if using self-signed for testing, import those) or fix your machine's certificate chain, or find out why your server is presenting an invalid certificate that can't be verified by your client.Oldworld
One thing I don't exactly like about this answer is the way it composes the JSON object, which is potentially exploitable via injection. A better way would be to compose an object and then transform it to JSON (with the proper escaping).Forland
@JohnWhite I agree, feels very ruby/js/pythonicAdonic
P
7

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)
Piercing answered 11/4, 2021 at 10:16 Comment(0)
N
6

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.
Nottinghamshire answered 27/6, 2014 at 15:9 Comment(0)
H
3

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

Handle answered 13/5, 2021 at 15:11 Comment(0)
C
3

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*.
Chirpy answered 4/4, 2023 at 1:57 Comment(0)
C
1

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.

Cappello answered 26/3, 2022 at 17:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.