如何说服UnmarshalJSON与切片子类型一起工作?

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

How do I convince UnmarshalJSON to work with a slice subtype?

问题

我想要使用base64RawURLEncoding而不是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

我尝试使用切片的指针,它可以工作(而且不需要反射),但是在其他需要使用切片而不是指针的代码中会引起其他问题。

所以,我有两个问题:

  1. 这是使用RawURLEncoding获取字节切片进行编组的最佳方法吗?
  2. 如果是的话,如何使我的字节切片子类型引用从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:

  1. Is this the best way to go about getting byte slices to marshal using RawURLEncoding?
  2. 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。

playground 示例

英文:

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.

playground example

huangapple
  • 本文由 发表于 2017年5月23日 12:13:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/44125690.html
匿名

发表评论

匿名网友

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

确定