203

I have an io.ReadCloser object (from an http.Response object).

What's the most efficient way to convert the entire stream to a string object?

djd
  • 4,988
  • 2
  • 25
  • 35

7 Answers7

239

EDIT:

Since 1.10, strings.Builder exists. Example:

buf := new(strings.Builder)
n, err := io.Copy(buf, r)
// check errors
fmt.Println(buf.String())

OUTDATED INFORMATION BELOW

The short answer is that it it will not be efficient because converting to a string requires doing a complete copy of the byte array. Here is the proper (non-efficient) way to do what you want:

buf := new(bytes.Buffer)
buf.ReadFrom(yourReader)
s := buf.String() // Does a complete copy of the bytes in the buffer.

This copy is done as a protection mechanism. Strings are immutable. If you could convert a []byte to a string, you could change the contents of the string. However, go allows you to disable the type safety mechanisms using the unsafe package. Use the unsafe package at your own risk. Hopefully the name alone is a good enough warning. Here is how I would do it using unsafe:

buf := new(bytes.Buffer)
buf.ReadFrom(yourReader)
b := buf.Bytes()
s := *(*string)(unsafe.Pointer(&b))

There we go, you have now efficiently converted your byte array to a string. Really, all this does is trick the type system into calling it a string. There are a couple caveats to this method:

  1. There are no guarantees this will work in all go compilers. While this works with the plan-9 gc compiler, it relies on "implementation details" not mentioned in the official spec. You can not even guarantee that this will work on all architectures or not be changed in gc. In other words, this is a bad idea.
  2. That string is mutable! If you make any calls on that buffer it will change the string. Be very careful.

My advice is to stick to the official method. Doing a copy is not that expensive and it is not worth the evils of unsafe. If the string is too large to do a copy, you should not be making it into a string.

Stephen Weinberg
  • 51,320
  • 14
  • 134
  • 113
  • Thanks, that's a really detailed answer. The "good" way seems roughly equivalent to @Sonia's answer too (since buf.String just does the cast internally). – djd Mar 11 '12 at 22:07
  • 1
    And it doesn't even work with my version, it seems not to be able to get a Pointer from &but.Bytes(). Using Go1. – sinni800 Jun 21 '12 at 00:56
  • @sinni800 Thanks for the tip. I forgot function returns were not addressable. It is now fixed. – Stephen Weinberg Jun 21 '12 at 01:30
  • 3
    Well computers are pretty damn fast at copying blocks of bytes. And given this is an http request, I can't imagine a scenario where transmission latency won't be a squillion times larger than the trivial time it takes to copy the byte array. Any functional language copies this type of immutable stuff around all over the place, and still runs plenty fast. – see sharper Aug 31 '16 at 06:45
  • This answer is out-of-date. `strings.Builder` does this efficiently by ensuring the underlying `[]byte` never leaks, and converting to `string` without a copy in a way that will be supported going forward. This didn't exist in 2012. @dimchansky's solution below has been the correct one since Go 1.10. Please consider an edit! – Nuno Cruces May 13 '20 at 12:23
159

Answers so far haven't addressed the "entire stream" part of the question. I think the good way to do this is ioutil.ReadAll. With your io.ReaderCloser named rc, I would write,

Go >= v1.16

if b, err := io.ReadAll(rc); err == nil {
    return string(b)
} ...

Go <= v1.15

if b, err := ioutil.ReadAll(rc); err == nil {
    return string(b)
} ...
aymericbeaumet
  • 6,853
  • 2
  • 37
  • 50
Sonia
  • 27,135
  • 8
  • 52
  • 54
  • 2
    Thanks, good answer. It looks like `buf.ReadFrom()` also reads the whole stream up to EOF. – djd Mar 11 '12 at 22:15
  • 11
    How funny: I just read the implementation of `ioutil.ReadAll()` and it simply wraps a `bytes.Buffer`'s `ReadFrom`. And the buffer's `String()` method is a simple wrap around casting to `string` – so the two approaches are practically the same! – djd Mar 11 '12 at 22:16
  • 1
    I did this and it works...the first time. For some reason after reading the string, the sequent reads return an empty string. Not sure why yet. – Aldo 'xoen' Giambelluca Feb 26 '15 at 17:18
  • 1
    @Aldo'xoen'Giambelluca ReadAll consumes the reader, so on the next call there is nothing left to read. – DanneJ Mar 18 '16 at 15:13
  • @DanneJ I wrote this some time ago: https://medium.com/@xoen/golang-read-from-an-io-readwriter-without-loosing-its-content-2c6911805361#.l2val31sd Are there any reasons to not do it? – Aldo 'xoen' Giambelluca Mar 21 '16 at 14:35
  • @Aldo'xoen'Giambelluca maybe I misunderstood what you meant. If I understand the blog post correctly, you read the data from the reader once, then create a new reader based on the byte array. That seems different from what we are discussing here? – DanneJ Mar 23 '16 at 11:49
  • @DanneJ correct. I had to do it as in my application as I just wanted to have a peak at the content but leave the stream intact for the future read. So I wrote the content back. – Aldo 'xoen' Giambelluca Mar 23 '16 at 15:10
  • I suppose most of the time you just need to consume the content only once so it's not a problem. – Aldo 'xoen' Giambelluca Mar 23 '16 at 15:10
  • @Aldo'xoen'Giambelluca If you need to peek some bytes then do peek it (see https://medium.com/@owlwalks/dont-parse-everything-from-client-multipart-post-golang-9280d23cd4ad), but don't read everything to memory. What if someone sends you 100MB several times? It is super easy to drain your resources. – Kangur Feb 27 '20 at 10:59
15
data, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(data))
Xavi
  • 20,111
  • 14
  • 72
  • 63
yakob abada
  • 1,101
  • 14
  • 20
6
func copyToString(r io.Reader) (res string, err error) {
    var sb strings.Builder
    if _, err = io.Copy(&sb, r); err == nil {
        res = sb.String()
    }
    return
}
Dimchansky
  • 644
  • 7
  • 6
5

The most efficient way would be to always use []byte instead of string.

In case you need to print data received from the io.ReadCloser, the fmt package can handle []byte, but it isn't efficient because the fmt implementation will internally convert []byte to string. In order to avoid this conversion, you can implement the fmt.Formatter interface for a type like type ByteSlice []byte.

  • Is the conversion from []byte to string expensive? I assumed string([]byte) didn't actually copy the []byte, but just interpreted the slice elements as a series of runes. That is why I suggested Buffer.String() http://weekly.golang.org/src/pkg/bytes/buffer.go?s=1787:1819#L37. I guess it would be good to know what is happening when string([]byte) is called. – Nate Mar 10 '12 at 16:46
  • 4
    Conversion from `[]byte` to `string` is reasonably fast, but the question was asking about "the most efficient way". Currently, the Go run-time will always allocate a new `string` when converting `[]byte` to `string`. The reason for this is that the compiler doesn't know how to determine whether the `[]byte` will be modified after the conversion. There is some room for compiler optimizations here. –  Mar 10 '12 at 17:56
2
var b bytes.Buffer
b.ReadFrom(r)

// b.String()
Vojtech Vitek - golang.cz
  • 25,275
  • 4
  • 34
  • 40
0

I like the bytes.Buffer struct. I see it has ReadFrom and String methods. I've used it with a []byte but not an io.Reader.

Nate
  • 5,237
  • 7
  • 42
  • 52