英文:
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)
英文:
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论