Reputation: 1
I found this cool interface recently, io.WriterTo
:
I would like to implement it for some JSON objects. I was able to make this:
package calendar
import (
"bytes"
"encoding/json"
"io"
)
type date struct {
Month int
Day int
}
func (d date) WriteTo(w io.Writer) (int64, error) {
buf := new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(d)
if err != nil {
return 0, err
}
return buf.WriteTo(w)
}
But I think it's not ideal, as it makes a copy of the object in memory, before sending to the Writer. Is it possible to write directly, but also know how many bytes were written?
Upvotes: 2
Views: 1038
Reputation: 24738
Is it possible to write directly, but also know how many bytes were written?
Coincidentally, it is what exercise 7.2 of the book The Go Programming Language is about. The exercise consists of implementing a function with the signature:
func CountingWriter(w io.Writer) (io.Writer, *int64)
The returned pointer to int64
must contain at any moment the number of bytes written to the returned io.Writer
. Essentially, the returned io.Writer
is a decorator as it enhances the functionality of the initial io.Writer
by updating a byte counter.
First, let's create the type writerFunc
, which has the same parametrization as the only method of io.Writer
, Write
:
type writerFunc func([]byte) (int, error)
Then, define the method Write(p []byte) (int, error)
on writerFunc
:
func (wf writerFunc) Write(p []byte) (int, error) {
return wf(p)
}
This way, writerFunc
satisfies io.Writer
and serves as an adapter for any func([]byte) (int, error)
into an io.Writer
– i.e., we can wrap a func([]byte) (int, error)
in a writerFunc
whenever an io.Writer
is required.
Finally, the CountingWriter
decorating function we are looking for:
func CountingWriter(w io.Writer) (io.Writer, *int64) {
count := new(int64)
writeAndCount := func(data []byte) (int, error) {
bytes, err := w.Write(data)
*count += int64(bytes)
return bytes, err
}
return writerFunc(writeAndCount), count
}
Note the last return
statement: the closure writeAndCount
is wrapped in a writerFunc
. This works because the closure's type is func([]byte) (int, error)
as well. As we have seen above, writerFunc
satisfies io.Writer
, which the caller of this function eventually receives.
Upvotes: 0
Reputation: 21
To write directly, create an io.Writer wrapper that counts the bytes written:
type countingWriter struct {
n int64
w io.Writer
}
func (cw *countingWriter) Write(p []byte) (int, error) {
n, err := cw.w.Write(p)
cw.n += int64(n)
return n, err
}
Change the WriteTo method to encode to a writer where the writer is the wrapper on the argument. Return the count of bytes and error when done.
func (d date) WriteTo(w io.Writer) (int64, error) {
cw := &countingWriter{w: w}
err := json.NewEncoder(cw).Encode(d)
return cw.n, err
}
Run an example on the the Go PlayGround
Upvotes: 2