Zombo
Zombo

Reputation: 1

Implement io.WriterTo with JSON

I found this cool interface recently, io.WriterTo:

https://godocs.io/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

Answers (2)

jfMR
jfMR

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

Markus
Markus

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

Related Questions