20

I've got a struct with a []uint8 member and I'm writing it with json.Marshal. Trouble is, it's interpreting the uint8s as chars and it outputs a string rather than an array of numbers.

I can get this to work if it's a []int, but I don't want to have to allocate and copy over the items if I can avoid it. Can I?

2
  • As I said in the question, I am using json.Marshal. My question is not about fmt, it's about the json library. Commented Jan 6, 2013 at 0:28
  • (I did have a comment asking about how I was outputting it, the author deleted it after I answered. Might as well keep my response.) Commented Jan 6, 2013 at 0:29

2 Answers 2

31

According to the docs, a []byte will be encoded as a Base64 string.

"Array and slice values encode as JSON arrays, except that []byte encodes as a base64-encoded string, and a nil slice encodes as the null JSON object."

So I think that you may need to make your struct implement the Marshaler interface by implementing your own MarshalJSON method that makes a more desirable JSON array encoding out of your []uint8.

Take this example:

import "fmt"
import "encoding/json"
import "strings"

type Test struct {
    Name  string
    Array []uint8
}

func (t *Test) MarshalJSON() ([]byte, error) {
    var array string
    if t.Array == nil {
        array = "null"
    } else {
        array = strings.Join(strings.Fields(fmt.Sprintf("%d", t.Array)), ",")
    }
    jsonResult := fmt.Sprintf(`{"Name":%q,"Array":%s}`, t.Name, array)
    return []byte(jsonResult), nil
}

func main() {
    t := &Test{"Go", []uint8{'h', 'e', 'l', 'l', 'o'}}

    m, err := json.Marshal(t)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%s", m) // {"Name":"Go","Array":[104,101,108,108,111]}
}

http://play.golang.org/p/Tip59Z9gqs


Or maybe a better idea would be to make a new type that has []uint8 as its underlying type, make that type a Marshaler, and use that type in your struct.

import "fmt"
import "encoding/json"
import "strings"

type JSONableSlice []uint8

func (u JSONableSlice) MarshalJSON() ([]byte, error) {
    var result string
    if u == nil {
        result = "null"
    } else {
        result = strings.Join(strings.Fields(fmt.Sprintf("%d", u)), ",")
    }
    return []byte(result), nil
}

type Test struct {
    Name  string
    Array JSONableSlice
}

func main() {
    t := &Test{"Go", []uint8{'h', 'e', 'l', 'l', 'o'}}

    m, err := json.Marshal(t)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%s", m) // {"Name":"Go","Array":[104,101,108,108,111]}
}

http://play.golang.org/p/6aURXw8P5d

Sign up to request clarification or add additional context in comments.

3 Comments

Brilliant. I think the second option is a bit more future-proof. Thanks very much.
@Joe: You're welcome. And I updated to make it a little more complete by handling a nil slice as null.
But the program isn't printing the alphabets correctly?
0

Here's another way, instead of using the slice []uint8, use the array [...]uint8:

marshal1, _ := json.Marshal([]uint8{32}) //"IA=="
marshal2, _ := json.Marshal([...]uint8{32}) //[32]

Also:

data := []uint8{32}
marshal, _ := json.Marshal((*[1]uint8)(data)) //after go1.17
marshal, _ := json.Marshal(*(*[1]uint8)(unsafe.Pointer(&data[0]))) //not recommended

Because go only treats byte slices specially (at least in the current version 1.23):

func newSliceEncoder(t reflect.Type) encoderFunc {
    // Byte slices get special treatment; arrays don't.
    if t.Elem().Kind() == reflect.Uint8 {
        p := reflect.PointerTo(t.Elem())
        if !p.Implements(marshalerType) && ! p.Implements(textMarshalerType) {
            return encodeByteSlice
        }
    }
    enc := sliceEncoder{newArrayEncoder(t)}
    return enc.encode
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.