Why can't I do json.Marshal directly to http.Response struct

I’m trying to do json.Marshal directly to http.Response but the returned []byte is nil. I can see the response from http.Get is a valid struct and thought json.Marshall should be able to parse it. Which point am I missing here?

http.Response{Status:"200 OK", StatusCode:200, Proto:"HTTP/1.1", ProtoMajor:1, ProtoMinor:1, Header:http.Header{"Pragma":[]string{"no-cache"}, "Content-Type":[]string{"text/html; charset=utf-8"}, "Connection":[]string{"keep-alive"}, "Expires":[]string{"-1"}, "Server":[]string{"Microsoft-IIS/7.5"}, "X-Powered-By":[]string{"ASP.NET"}, "Cache-Control":[]string{"no-cache"}, "Vary":[]string{"Accept-Encoding"}, "X-Aspnet-Version":[]string{"4.0.30319"}, "Date":[]string{"Wed, 07 Jun 2017 16:35:51 GMT"}, "Age":[]string{"0"}}, Body:(*http.gzipReader)(0xc4201460c0), ContentLength:-1, TransferEncoding:[]string(nil), Close:false, Uncompressed:true, Trailer:http.Header(nil), Request:(*http.Request)(0xc42000a900), TLS:(*tls.ConnectionState)(nil)}

You forgot to include the code you use. Please also try to make snippets more readable.

1 Like

sorry, here we go.

I’d expect json.Marshall can output the struct fields like {“Struct”: “200 OK”…}

package main

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

func main() {

	resp, _ := http.Get("http://www.httpbin.org")
	fmt.Printf("%#v\n", *resp)

	respJ, _ := json.Marshal(resp)
	fmt.Println("---")
	fmt.Println(respJ)


}

the result is

http.Response{Status:"200 OK", StatusCode:200, Proto:"HTTP/1.1", ProtoMajor:1, ProtoMinor:1, Header:http.Header{"C
ontent-Type":[]string{"text/html; charset=utf-8"}, "Content-Length":[]string{"12793"}, "Access-Control-Allow-Crede
ntials":[]string{"true"}, "X-Powered-By":[]string{"Flask"}, "Connection":[]string{"keep-alive"}, "Server":[]string
{"meinheld/0.6.1"}, "Date":[]string{"Wed, 07 Jun 2017 18:33:42 GMT"}, "Access-Control-Allow-Origin":[]string{"*"},
 "X-Processed-Time":[]string{"0.00553107261658"}, "Via":[]string{"1.1 vegur"}}, Body:(*http.bodyEOFSignal)(0xc4200
90380), ContentLength:12793, TransferEncoding:[]string(nil), Close:false, Uncompressed:false, Trailer:http.Header(
nil), Request:(*http.Request)(0xc4200ea300), TLS:(*tls.ConnectionState)(nil)}
---
[]
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

func main() {

	resp, _ := http.Get("http://www.httpbin.org")
	fmt.Printf("%#v\n", *resp)

	respJ, err := json.Marshal(resp)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("---")
	fmt.Println(respJ)

}

produces

http.Response{Status:"200 OK", StatusCode:200, Proto:"HTTP/1.1", ProtoMajor:1, ProtoMinor:1, Header:http.Header{"Content-Length":[]string{"12793"}, "Access-Control-Allow-Credentials":[]string{"true"}, "Via":[]string{"1.1 vegur"}, "Connection":[]string{"keep-alive"}, "Date":[]string{"Wed, 07 Jun 2017 18:43:14 GMT"}, "Content-Type":[]string{"text/html; charset=utf-8"}, "Access-Control-Allow-Origin":[]string{"*"}, "X-Powered-By":[]string{"Flask"}, "X-Processed-Time":[]string{"0.00565099716187"}, "Server":[]string{"meinheld/0.6.1"}}, Body:(*http.bodyEOFSignal)(0xc4200e6240), ContentLength:12793, TransferEncoding:[]string(nil), Close:false, Uncompressed:false, Trailer:http.Header(nil), Request:(*http.Request)(0xc42000a800), TLS:(*tls.ConnectionState)(nil)}
2017/06/07 20:43:14 json: unsupported type: func() (io.ReadCloser, error)
exit status 1

That is caused by

Body io.ReadCloser

which above is

Body:(*http.bodyEOFSignal)(0xc4200e6240)

Don’t just ignore err!

1 Like

Thanks, my bad.

any explanation for this message?

which part of json.Marshal code doesn’t support “func() (io.ReadCloser, error)”? I was trying to trace down the source code but couldn’t figure out myself.

What do you expect io.ReadCloser to be serialized to?

I hope we are not trying to solve a XY Problem here. Why are you trying to serialize http.Response to JSON in the first place?

Have you read this?
https://golang.org/pkg/encoding/json/#Marshal

Based on that it looks like http.Response doesn’t implement a marshaller. Therefore the nil []byte.
https://golang.org/pkg/net/http/#Response

You probably are going to have to create a struct for the JSON output format you desire and marshal that.

type MyJson struct {
    Val1 string `json:"val1"`
    Val2 int    `json:"val2"`
}
1 Like

Thanks. I thought json.Marshal could deal with io.reader hence the confusion.

On the same topic, can anyone educate me what’s the meaning and use cases for a struct has interface as its field like the following in golang nett/http package? and how it’s different from emdedding “io.ReadCloser” in Response struct?

type Response struct {
    //...
	Body io.ReadCloser
	//...
}

How would that look like? If you ask for the difference between A and B, please show both A and B, not only A.

thanks. I see bodyEOFSignal struct has a field body with dynamic type http.body struct http.bodyEOFSignal{body:(*http.body)(0xc4200ee200) and both structs are ReadCloser to work with ioutil.ReadAll. have a better understanding why my original problem was.

  func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
  	es.mu.Lock()
  	closed, rerr := es.closed, es.rerr
  	es.mu.Unlock()
  	if closed {
  		return 0, errReadOnClosedResBody
  	}
  	if rerr != nil {
  		return 0, rerr
  	}
  
  	n, err = es.body.Read(p)
  	if err != nil {
  		es.mu.Lock()
  		defer es.mu.Unlock()
  		if es.rerr == nil {
  			es.rerr = err
  		}
  		err = es.condfn(err)
  	}
  	return
  }

Here’s a brief explanation.

type field struct {
	Body io.ReadCloser
}

type embedded struct {
	io.ReadCloser
}

As defined here (i.e., without any other code), the main difference between these types is their method sets. field does not have any methods. embedded has two methods: Read(p []byte) (n int, err error) and Close() error. These methods were promoted from the embedded io.ReadCloser.

Closeing the io.ReadCloser can be done by:

  • field.Body.Close()
  • embedded.Close() (using the promoted method)
  • embedded.ReadCloser.Close() (the type name of embedded fields acts as the field name)

The promotion rules are defined in the spec.

1 Like

good summary!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.