theory theory - 1 year ago 86
JSON Question

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

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?


Answer Source

Use this code to unmarshal the value:

func (thing *Thing) UnmarshalJSON(data []byte) error {
  if len(data) == 0 || data[0] == 'n' { // null
    *thing = []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
  }
  *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

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download