如何说服UnmarshalJSON使用切片子类型?

时间:2017-05-23 04:13:20

标签: json go reflection marshalling slice

我想要使用base64 RawURLEncoding代替StdEncoding在JSON中编组和解组的字节切片。没有明显的方法可以通过encoding/json package来做到这一点,这是明智的,所以我想我会创建一个子类型来做这件事。

type Thing []byte

Marshaling支持很简单:

func (thing Thing) MarshalJSON() ([]byte, error) {
    if thing == nil {
        return []byte("null"), nil
    }
    return []byte(`"` + base64.RawURLEncoding.EncodeToString(thing) + `"`), nil
}

但Unmarshal并非如此。我跟踪了encoding/json source,并提出了:

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格式解码的数据?

1 个答案:

答案 0 :(得分:4)

使用此代码解组值:

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
}

关键点:

  • 使用指针接收器。
  • 不需要反射来为事物分配[]字节。

playground example