将JSON解码为接口值

huangapple go评论83阅读模式
英文:

Decode JSON into Interface Value

问题

由于encoding/json需要一个非nil接口来进行解组:我如何可靠地创建一个用户提供的指针类型的(完整)副本,将其存储在我的User接口中,然后将其JSON解码为临时的副本?

注意:这里的目标是进行“无人值守”操作-也就是说,从Redis/BoltDB中获取字节,解码为接口类型,然后检查接口定义的GetID()方法返回一个非空字符串,使用请求中间件。

Playground: http://play.golang.org/p/rYODiNrfWw

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"

	"time"
)

type session struct {
	ID      string
	User    User
	Expires int64
}

type User interface {
	GetID() string
}

type LocalUser struct {
	ID      string
	Name    string
	Created time.Time
}

func (u *LocalUser) GetID() string {
	return u.ID
}

type Auth struct {
	key []byte
	// We store an instance of userType here so we can unmarshal into it when
	// deserializing from JSON (or other non-gob decoders) into *session.User.
	// Attempting to unmarshal into a nil session.User would otherwise fail.
	// We do this so we can unmarshal from our data-store per-request *without
	// the package user having to do so manually* in the HTTP middleware. We can't
	// rely on the user passing in an fresh instance of their User satisfying type.
	userType User
}

func main() {
	// auth is a pointer to avoid copying the struct per-request: although small
	// here, it contains a 32-byte key, options fields, etc. outside of this example.
	var auth = &Auth{key: []byte("abc")}
	local := &LocalUser{"19313", "Matt", time.Now()}

	b, _, _, err := auth.SetUser(local)
	if err != nil {
		log.Fatalf("SetUser: %v", err)
	}

	user, err := auth.GetUser(b)
	if err != nil {
		log.Fatalf("GetUser: %#v", err)
	}

	fmt.Fprintf(os.Stdout, "%v\n", user)

}

func (auth *Auth) SetUser(user User) (buf []byte, id string, expires int64, err error) {
	sess := newSession(user)

	// Shallow copy the user into our config. struct so we can copy and then unmarshal
	// into it in our middleware without requiring the user to provide it themselves
	// at the start of every request
	auth.userType = user

	b := bytes.NewBuffer(make([]byte, 0))
	err = json.NewEncoder(b).Encode(sess)
	if err != nil {
		return nil, id, expires, err
	}

	return b.Bytes(), sess.ID, sess.Expires, err
}

func (auth *Auth) GetUser(b []byte) (User, error) {
	sess := &session{}

	// Another shallow copy, which means we're re-using the same auth.userType
	// across requests (bad).
	// Also note that we need to copy into it session.User so that encoding/json
	// can unmarshal into its fields.
	sess.User = auth.userType

	err := json.NewDecoder(bytes.NewBuffer(b)).Decode(sess)
	if err != nil {
		return nil, err
	}

	return sess.User, err
}

func (auth *Auth) RequireAuth(h http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		// e.g. user, err := store.Get(r, auth.store, auth.userType)
		// This would require us to have a fresh instance of userType to unmarshal into
		// on each request.

		// Alternative might be to have:
		// func (auth *Auth) RequireAuth(userType User) func(h http.Handler) http.Handler
		// e.g. called like http.Handle("/monitor", RequireAuth(&LocalUser{})(SomeHandler)
		// ... which is clunky and using closures like that is uncommon/non-obvious.
	}

	return http.HandlerFunc(fn)
}

func newSession(u User) *session {
	return &session{
		ID:      "12345",
		User:    u,
		Expires: time.Now().Unix() + 3600,
	}
}
英文:

As encoding/json needs a non-nil interface to unmarshal into: how can I reliably make a (full) copy of a user-provided pointer type, store that in my User interface, and then JSON decode into that ad-hoc?

Note: the goal here is to do this 'unattended' - that is, pull the bytes from Redis/BoltDB, decode into the interface type, and then check that the GetID() method the interface defines returns a non-empty string, with request middleware.

Playground: http://play.golang.org/p/rYODiNrfWw

package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
)
type session struct {
ID      string
User    User
Expires int64
}
type User interface {
GetID() string
}
type LocalUser struct {
ID      string
Name    string
Created time.Time
}
func (u *LocalUser) GetID() string {
return u.ID
}
type Auth struct {
key []byte
// We store an instance of userType here so we can unmarshal into it when
// deserializing from JSON (or other non-gob decoders) into *session.User.
// Attempting to unmarshal into a nil session.User would otherwise fail.
// We do this so we can unmarshal from our data-store per-request *without
// the package user having to do so manually* in the HTTP middleware. We can't
// rely on the user passing in an fresh instance of their User satisfying type.
userType User
}
func main() {
// auth is a pointer to avoid copying the struct per-request: although small
// here, it contains a 32-byte key, options fields, etc. outside of this example.
var auth = &Auth{key: []byte("abc")}
local := &LocalUser{"19313", "Matt", time.Now()}
b, _, _, err := auth.SetUser(local)
if err != nil {
log.Fatalf("SetUser: %v", err)
}
user, err := auth.GetUser(b)
if err != nil {
log.Fatalf("GetUser: %#v", err)
}
fmt.Fprintf(os.Stdout, "%v\n", user)
}
func (auth *Auth) SetUser(user User) (buf []byte, id string, expires int64, err error) {
sess := newSession(user)
// Shallow copy the user into our config. struct so we can copy and then unmarshal
// into it in our middleware without requiring the user to provide it themselves
// at the start of every request
auth.userType = user
b := bytes.NewBuffer(make([]byte, 0))
err = json.NewEncoder(b).Encode(sess)
if err != nil {
return nil, id, expires, err
}
return b.Bytes(), sess.ID, sess.Expires, err
}
func (auth *Auth) GetUser(b []byte) (User, error) {
sess := &session{}
// Another shallow copy, which means we're re-using the same auth.userType
// across requests (bad).
// Also note that we need to copy into it session.User so that encoding/json
// can unmarshal into its fields.
sess.User = auth.userType
err := json.NewDecoder(bytes.NewBuffer(b)).Decode(sess)
if err != nil {
return nil, err
}
return sess.User, err
}
func (auth *Auth) RequireAuth(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
// e.g. user, err := store.Get(r, auth.store, auth.userType)
// This would require us to have a fresh instance of userType to unmarshal into
// on each request.
// Alternative might be to have:
// func (auth *Auth) RequireAuth(userType User) func(h http.Handler) http.Handler
// e.g. called like http.Handle("/monitor", RequireAuth(&LocalUser{})(SomeHandler)
// ... which is clunky and using closures like that is uncommon/non-obvious.
}
return http.HandlerFunc(fn)
}
func newSession(u User) *session {
return &session{
ID:      "12345",
User:    u,
Expires: time.Now().Unix() + 3600,
}
}

答案1

得分: 1

如果你需要深拷贝一个接口,将该方法添加到你的接口中。

type User interface {
  GetID() string
  Copy() User
}

type LocalUser struct {
  ID string
  Name string
  Created time.Time
}

// Copy 接收一个 LocalUser 的副本并返回一个指向它的指针。
func (u LocalUser) Copy() User {
  return &u
}
英文:

If you need to deep copy an interface, add that method to your interface.

type User interface {
GetID() string
Copy() User
}
type LocalUser struct {
ID string
Name string
Created time.Time
}
// Copy receives a copy of LocalUser and returns a pointer to it.
func (u LocalUser) Copy() User {
return &u
}

答案2

得分: 1

由于应用程序将解码为User,而JSON解码器的参数必须是指针值,我们可以假设User值是指针值。在这个假设下,可以使用以下代码来创建一个新的零值进行解码:

uzero := reflect.New(reflect.TypeOf(u).Elem()).Interface().(User)

playground示例

英文:

Because the application will decode to a User and the argument to the JSON decoder must be a pointer value, we can assume that User values are pointer values. Given this assumption, the following code can be used to create a new zero value for decoding:

uzero := reflect.New(reflect.TypeOf(u).Elem()).Interface().(User)

playground example

huangapple
  • 本文由 发表于 2016年2月17日 11:42:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/35447480.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定