11

I need some help with unmarshaling. I have this example code:

package main

import (
    "encoding/json"
    "fmt"
)

type Obj struct {
    Id   string `json:"id"`
    Data []byte `json:"data"`
}

func main() {
    byt := []byte(`{"id":"someID","data":["str1","str2"]}`)

    var obj Obj
    if err := json.Unmarshal(byt, &obj); err != nil {
        panic(err)
    }

    fmt.Println(obj)
}

What I try to do here - convert bytes to the struct, where type of one field is []byte. The error I get:

panic: json: cannot unmarshal string into Go struct field Obj.data of type uint8

That's probably because parser already sees that "data" field is already a slice and tries to represent "str1" as some char bytecode (type uint8?).

How do I store the whole data value as one bytes array? Because I want to unmarshal the value to the slice of strings later. I don't include a slice of strings into struct because this type can change (array of strings, int, string, etc), I wish this to be universal.

4
  • You are presenting two strings and want them to fit in a single byte-slice? Commented Jun 2, 2018 at 19:09
  • I want the whole ["str1","str2"] to be a []byte Commented Jun 2, 2018 at 19:12
  • You mean you want the whole string '["str1","str2"]' to be a []byte? Commented Jun 2, 2018 at 19:15
  • Correct, independently of the content. It just looks like an array only in this specific case Commented Jun 2, 2018 at 19:19

3 Answers 3

12

If []byte really is what you want, use json.RawMessage, which is of type []byte, but also implements the methods for JSON parsing. I believe this may be what you want, as it will accept whatever ends up in data. Of course, you then have to manually parse Data to figure out just what actually IS in there.

One possible bonus is that this skips any heavy parsing because it just copies the bytes over. When you want to use this data for something, you use a []interface{}, then use a type switch to use individual values.

https://play.golang.org/p/og88qb_qtpSGJ

package main

import (
    "encoding/json"
    "fmt"
)

type Obj struct {
    Id   string          `json:"id"`
    Data json.RawMessage `json:"data"`
}

func main() {
    byt := []byte(`{"id":"someID","data":["str1","str2", 1337, {"my": "obj", "id": 42}]}`)

    var obj Obj
    if err := json.Unmarshal(byt, &obj); err != nil {
        panic(err)
    }

    fmt.Printf("%+v\n", obj)
    fmt.Printf("Data: %s\n", obj.Data)

    // use it
    var d []interface{}
    if err := json.Unmarshal(obj.Data, &d); err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", d)

    for _, v := range d {
        // you need a type switch to deterine the type and be able to use most of these
        switch real := v.(type) {
        case string:
            fmt.Println("I'm a string!", real)
        case float64:
            fmt.Println("I'm a number!", real)
        default:
            fmt.Printf("Unaccounted for: %+v\n", v)
        }

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

7 Comments

Probably no, data can be anything - array of something, string, int, etc. In this specific case I want obj.Data to be `["str1","str2"]` - notice ` char, so this should be only bytes, not an array of something
Looks interesting! Isn't "RawMessage" type more expensive than interface{} or []byte? (just asking)
@Alexey Addressed that question in answer and added usage for RawMessage.
If you care about expensive, then json is not a very good choice for interchanging data, because json is expensive because of all the conversions to do and structures to create. Take a look at protocol-buffers instead, they are designed to avoid the expensive processing of json
Unless you actually need it to be a []byte, then @Venantius has the better answer.
|
7

My first recommendation would be for you to just use []string instead of []byte if you know the input type is going to be an array of strings.

If data is going to be a JSON array with various types, then your best option is to use []interface{} instead - Go will happily unmarshal the JSON for you and you can perform checks at runtime to cast those into more specific typed variables on an as-needed basis.

1 Comment

This is actually probably better than using json.RawMessage as in my answer. I'd say this is really what should be done, but I chose to answer the question more specifically ;)
1

Your question is:

convert bytes array to struct with a field of type []byte

But you do not have a bytearray but a string array. Your question is not the same as your example. So let answer your question, there are more solutions possible depending in how far you want to diverge from your original requirements.

One string can be converted to one byte-slice, two strings need first to be transformed to one string. So that is problem one. The second problem are the square-brackets in your json-string

This works fine, it implicitly converts the string in the json-string to a byte-slice:

byt := []byte(`{"id":"someID","data":"str1str2"}`)

var obj Obj
if err := json.Unmarshal(byt, &obj); err != nil {
    panic(err)
}

fmt.Println(obj)

3 Comments

He doesn't care about the contents of the array. He wants the whole thing as a []byte.
True, in another case it could be not an array at all, but something else, like an integer. So instead of [1,2] try to see [5]byte, where the first and the last bytes are chars of square-brackets
I think, in that case, the answer of RayfenWindspear is more useful for you, although it does not give you a byte-slice. But if you do not care for that, then the answer of Venantius is also appropriate and much easier to implement. I was responding within the defined limits of your original question.

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.