英文:
How do I convince UnmarshalJSON to work with a slice subtype?
问题
我想要使用base64的RawURLEncoding
而不是StdEncoding
,将字节切片编组和解组为JSON。通过encoding/json包,没有明显的方法可以实现这一点,这是可以理解的,所以我想创建一个子类型来完成。
type Thing []byte
编组支持很容易实现:
func (thing Thing) MarshalJSON() ([]byte, error) {
if thing == nil {
return []byte("null"), nil
}
return []byte(`"` + base64.RawURLEncoding.EncodeToString(thing) + `"`), nil
}
但是解组就不那么简单了。我追踪了encoding/json源码,得到了以下代码:
func (thing Thing) UnmarshalJSON(data []byte) error {
v := reflect.ValueOf(&thing)
if len(data) == 0 || data[0] == 'n' { // null
v.SetBytes([]byte{})
return nil
}
data = data[1 : len(data)-1]
dst := make([]byte, base64.RawURLEncoding.DecodedLen(len(data)))
n, err := base64.RawURLEncoding.Decode(dst, data)
if err != nil {
return err
}
v.SetBytes(Thing(dst[:n]))
return nil
}
但是在调用SetBytes()
时会导致恐慌:
panic: reflect: reflect.Value.SetBytes using unaddressable value [recovered]
panic: reflect: reflect.Value.SetBytes using unaddressable value
我尝试使用切片的指针,它可以工作(而且不需要反射),但是在其他需要使用切片而不是指针的代码中会引起其他问题。
所以,我有两个问题:
- 这是使用
RawURLEncoding
获取字节切片进行编组的最佳方法吗? - 如果是的话,如何使我的字节切片子类型引用从
RawURLEncoding
格式解码的数据?
英文:
I want byte slices that marshal and unmarshal in JSON using base64 RawURLEncoding
instead of StdEncoding
. There's no obvious way to do this through the encoding/json package, which is sensible, so I thought I'd create a subtype to do it.
type Thing []byte
Marshaling support is easy:
func (thing Thing) MarshalJSON() ([]byte, error) {
if thing == nil {
return []byte("null"), nil
}
return []byte(`"` + base64.RawURLEncoding.EncodeToString(thing) + `"`), nil
}
But Unmarshal not so much. I traced the encoding/json source, and came up with:
func (thing Thing) UnmarshalJSON(data []byte) error {
v := reflect.ValueOf(&thing)
if len(data) == 0 || data[0] == 'n' { // null
v.SetBytes([]byte{})
return nil
}
data = data[1 : len(data)-1]
dst := make([]byte, base64.RawURLEncoding.DecodedLen(len(data)))
n, err := base64.RawURLEncoding.Decode(dst, data)
if err != nil {
return err
}
v.SetBytes(Thing(dst[:n]))
return nil
}
But yields a panic in the call to SetBytes()
:
panic: reflect: reflect.Value.SetBytes using unaddressable value [recovered]
panic: reflect: reflect.Value.SetBytes using unaddressable value
I tried using a pointer to a slice, instead, which works (and doesn't require reflection), but causes other challenges elsewhere in my code that wants to work with slices instead of pointers.
So two questions, I guess:
- Is this the best way to go about getting byte slices to marshal using
RawURLEncoding
? - If so, how can I convince my byte slice subtype to reference the data decoded from the
RawURLEncoding
format?
答案1
得分: 5
使用以下代码来解组值:
func (thing *Thing) UnmarshalJSON(data []byte) error {
if len(data) == 0 || data[0] == 'n' { // 从问题中复制的,可以改进
*thing = nil
return nil
}
data = data[1 : len(data)-1]
dst := make([]byte, base64.RawURLEncoding.DecodedLen(len(data)))
n, err := base64.RawURLEncoding.Decode(dst, data)
if err != nil {
return err
}
*thing = dst[:n]
return nil
}
关键点:
- 使用指针接收器。
- 不需要使用反射将 []byte 赋值给 Thing。
英文:
Use this code to unmarshal the value:
func (thing *Thing) UnmarshalJSON(data []byte) error {
if len(data) == 0 || data[0] == 'n' { // copied from the Q, can be improved
*thing = nil
return nil
}
data = data[1 : len(data)-1]
dst := make([]byte, base64.RawURLEncoding.DecodedLen(len(data)))
n, err := base64.RawURLEncoding.Decode(dst, data)
if err != nil {
return err
}
*thing = dst[:n]
return nil
}
The key points:
- Use a pointer receiver.
- Reflection is not needed to assign a []byte to a Thing.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论