Skip to content

encoding/json: clarify what happens when unmarshaling into a non-empty interface{} #26946

Open
@deuill

Description

@deuill

What version of Go are you using (go version)?

go version go1.10.3 linux/amd64

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOCACHE="/home/deuill/.cache/go-build"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/deuill/.go"
GORACE=""
GOROOT="/usr/lib/go"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build634949289=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Link to Play: https://play.golang.org/p/37E1QHWofMy

Passing a pointer to a struct (anonymous or not) as type interface{} to json.Unmarshal will have the original type replaced with a map[string]interface{} (correctly decoded, however). It appears the difference between correct and incorrect result is changing:

var nopointer interface{} = Object{}

to:

var nopointer = Object{}

In the example above. A interface{} containing a pointer to a struct works as expected, e.g.:

var pointer interface{} = &Object{}

is the same as:

var pointer = &Object{}

What did you expect to see?

The following results, in order of desirability:

  • The correct struct type (Object, for the example above) populated with data from the JSON object given.
  • An error denoting the inability to unmarshal into the underlying type given.
  • The behaviour above documented in the documentation for json.Unmarshal.

What did you see instead?

None of the above? The use case that led to this issue for me was giving constructors types, where these types would be used as "templates" of sorts for populating with data in subsequent requests. For example:

type Decoder interface {
    Decode(*http.Request) (interface{}, error)
}

type RequestHandler func(interface{}) (interface{}, error)

type Request struct {
    Value interface{}
}

func (r Request) Decode(r *http.request) (interface{}, error) {
    // Read body from request
    // ...

    if err := json.Unmarshal(body, &r.Value); err != nil {
        return nil, err
    }

    return r, nil
}

type User struct {
    Name string `json:"name"`
}

func NewHandler(decoder Decoder, handler RequestHandler) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        req, err := decoder.Decode(r)
        if err != nil {
            return
        }

        resp, err := handler(req)
        if err != nil {
            return
        }

        // Encode response into ResponseWriter etc.
        // ...
    }
}

It is implied, then, that NewHandler is called with the bare type:

NewHandler(Request{Value: User{}}, userHandler)

Which will give "fresh" values to the RequestHandler each time, as they emerge from Request.Decode. If given a pointer, i.e.:

NewHandler(Request{Value: &User{}}, userHandler)

Previous requests will populate the pointer to User, leaving garbage data for future requests.
Apologies for the perhaps obtuse example.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DocumentationIssues describing a change to documentation.NeedsFixThe path to resolution is known, but the work has not been done.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions