Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

go

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.

like image 354
Alexey Avatar asked Dec 07 '22 14:12

Alexey


2 Answers

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)
        }

    }
}
like image 182
RayfenWindspear Avatar answered Dec 11 '22 10:12

RayfenWindspear


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.

like image 22
Venantius Avatar answered Dec 11 '22 11:12

Venantius