interface{} to []string - go

I'm trying to parse some YAML file into go struct, but the file itself can not be treated as ordinary go structure: some of values may be either string or map[string][]string.
What I have tried is to do custom unmarshal func:
func (domain *Domain) UnmarshalYAML(unmarshal func(interface{}) error) error {
fmt.Println("Parsing domain")
var hostUrl interface{}
unmarshal(&hostUrl)
fmt.Println(reflect.TypeOf(hostUrl))
switch hostUrl.(type) {
case string:
domain.Host = hostUrl.(string)
case map[interface {}]interface {}:
fmt.Println("got map")
v := reflect.ValueOf(hostUrl)
fmt.Println(v.MapKeys()[0])
for _, host := range v.MapKeys() {
domain.Host = host.Interface().(string)
fmt.Println(v.MapIndex(host))
//HERE I NEED TO DO SMTH LIKE THIS:
//domain.Urls = v.MapIndex(host).([]string)
}
default:
return errors.New("invalid config file, cant parse domains")
}
return nil
}
My domain structure looks like this:
type Domain struct {
Host string
Urls []string
}
But this code causes an error:
invalid type assertion: v.MapIndex(host).([]string) (non-interface type reflect.Value on left)
So my question may sound like "how to convert {}interface to []string?" or it may become more complex: "How to parse YAML file into go struct if some key can be either simple string or map[string][]string?"
UPDATE:
Responding to #mkopriva:
fmt.Println(v.MapIndex(host))
for url := range v.MapIndex(host).Interface().([] interface{}) {
fmt.Println(url)
}
Didnt help me, as now it just prints some integers (0), but there should be a string. Converting it to an array of strings throws another error:
panic: interface conversion: interface {} is []interface {}, not []string

Thanks to #mkopriva and the snippet from the sandbox. The reason of integer appearing during iterating over v.MapIndex(host).Interface().([] interface{}) is that range returns two values: index and corresponding to that index value. I was only catching the first one. It is why I wasn't able to cast it to string.
Working loop:
for _, url := range v.MapIndex(host).Interface().([] interface{}) {
fmt.Println(url.(string))
domain.Urls = append(domain.Urls,url.(string) )
}

Related

How to return key's value of a map of type empty interface

I have taken a variable like var u = make(map[string]interface{}) which means that a key could hold a string/int or another map.
When I do the following it gives error cannot use v (type interface {}) as type string in return argument: need type assertion which looks obvious as the generic map have no idea what should it search. How can I resolve the issue? The code is given below(DO note that currently, the map is entirely empty)
var u = make(map[string]interface{})
// Get function retrieves the value of the given key. If failed, it returns error.
func Get(k string) (string, error) {
v, found := u[k]
println(reflect.Type(v))
if found {
v = u[k]
return v, nil
}
return v, errors.New(-1)
}
v, found := u[k] here v is interface{} type
But your function return type is (string, nil) where you are returning (v, nil) or (interface{}, nil).
interface{} can not convert into string automatically, need type assertion.
data, ok := v.(string)
You can return interface{} also and the consumer can decide which type it will converted.
I'm not sure what's your question. But you're getting this error because you are trying to return interface{} as concrete type string. If you want to return string, and you're sure that value of map is always string(then why are you using map[string]interface{} instead of map[string]string?) you can get underlying type of interface by using type assertion:
s, ok := v.(string)

How can I convert a JSON string to a byte array?

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

Does type assertion change the value in go?

Go newbie here.
I have a map where the key arguments should be []string.
However, if I try to use the value directly arguments := m["arguments"] it doesn't seem to be the right type. When used later to append to another slice with arguments... I get Cannot use 'arguments' (type interface{}) as type []string.
I fixed this by chaning the assignment to a type check arguments, _ := m["arguments"].([]string). That works, but I'm not sure why. Is type assertion doing conversion as well?
The full example is below:
import (
"github.com/fatih/structs"
"strings"
)
var playbookKeyDict = map[string]string{
"Playbook": "",
"Limit" : "--limit",
"ExtraVars" : "--extra-vars",
}
type Playbook struct {
Playbook string `json:"playbook" xml:"playbook" form:"playbook" query:"playbook"`
Limit string `json:"limit" xml:"limit" form:"limit" query:"limit"`
ExtraVars string `json:"extra-vars" xml:"extra-vars" form:"extra-vars" query:"extra-vars"`
Arguments []string `json:"arguments" xml:"arguments" form:"arguments" query:"arguments"`
Args []string
}
func (p *Playbook) formatArgs() {
// is it worth iterating through directly with reflection instead of using structs import?
// https://stackoverflow.com/questions/21246642/iterate-over-string-fields-in-struct
m := structs.Map(p)
// direct assignment has the wrong type?
// arguments := m["arguments"]
arguments, _ := m["arguments"].([]string)
delete(m, "arguments")
for k, v := range m {
// Ignore non-strings and empty strings
if val, ok := v.(string); ok && val != "" {
key := playbookKeyDict[k]
if key == "" {
p.Args = append(p.Args, val)
} else {
p.Args = append(p.Args, playbookKeyDict[k], val)
}
}
}
p.Args = append(p.Args, arguments...)
}
Type assertion is used to get a value wrapped around using interface.
m := structs.Map(p)
Map(v interface{}){}
Map function is actually taking interface as its argument in the case stated. It is wrapping the type which is []string and its underlying value which is slice. The type can be checked using Relection reflect.TypeOf().
func TypeOf(i interface{}) Type
According to Russ Cox blog on Interfaces
Interface values are represented as a two-word pair giving a pointer
to information about the type stored in the interface and a pointer to
the associated data.
As specified in Golang spec
For an expression x of interface type and a type T, the primary
expression
x.(T)
asserts that x is not nil and that the value stored in x is of type T.
The notation x.(T) is called a type assertion.
For the error part:-
Cannot use 'arguments' (type interface{}) as type []string
We first needs to get the underlying value of type []string from interface using type assertion.

golang, type int does not support indexing

I have a trouble in a part of code. I'm writing on revel framework(to be clear). This is a Worker go routine, and I want it to do several things:
switch the struct type of the stat variable, according to the
source, that would come. I made a switch, but before the all other
code would be correct, I don't really know if switch is written
properly.
I get cache for the date, and put it in new Work item.
I send Work to channel
here is what I got by now:
func worker(in <-chan Task, out chan <- Work, wg *sync.WaitGroup) {
for t := range in {
for sourceName, charts := range t.Request.Charts {
var stat interface{}
switch sourceName {
case "noagg":
stat = stat.([]NoaggModel)
case "oracle":
stat = stat.([]OracleModel)
default:
panic("Invalid type for Work model!")
}
w := Work{Name:"", Data:""}
err := cache.Get(string(sourceName)+"_"+string(t.Date), &stat);
for chart := range charts{
w.Name = chart["name"]
if err == nil{
w.Data = countDataByName( stat, t.Request.Filters, string(chart["name"]))
}
out <- w
}
}
}
wg.Done() // this worker is now done; let the WaitGroup know.
}
But now I got error that invalid operation: chart["name"] (type int does not support indexing)
But I have structs :
type Chart struct {
Name string `json:"name"`
Type string `json:"type"`
}
type Filter struct {
DayStart string `json:"dayStart"`
DayEnd string `json:"dayEnd"`
TimePeriods interface{} `json:"timePeriods"`
Lines []string `json:"lines"`
}
type Task struct {
Date string
Request ChartOptins
}
type Work struct {
Name string
Data interface{}
}
How should I write in a better way the correct switch, if the type of struct for cache can be different, and why is my name adding is bad and call error?
The for in the slice is missing a variable
for chart := range charts{
when iterating on a slice the first variable is the key and the second is the real value you want. In this case you can omit the key (an int) so the proper instruction should be
for _, chart := range charts{

In golang, how to embed on custom type?

I have custom types Int64Array, Channel and ChannelList like:
type Int64Array []int64
func (ia *Int64Array) Scan(src interface{}) error {
rawArray := string(src.([]byte))
if rawArray == "{}" {
*ia = []int64{}
} else {
matches := pgArrayPat.FindStringSubmatch(rawArray)
if len(matches) > 1 {
for _, item := range strings.Split(matches[1], ",") {
i, _ := strconv.ParseInt(item, 10, 64)
*ia = append(*ia, i)
}
}
}
return nil
}
func (ia Int64Array) Value() (driver.Value, error) {
var items []string
for _, item := range ia {
items = append(items, strconv.FormatInt(int64(item), 10))
}
return fmt.Sprintf("{%s}", strings.Join(items, ",")), nil
}
type Channel int64
type ChannelList []Channel
How can I embed Int64Array to ChannelList such that I can call Scan and Value methods on it? I tried the following:
type ChannelList []Channel {
Int64Array
}
but I'm getting syntax error. What's important is to make sure ChannelList items are of type Channel, if this isn't possible via embedding I might just create stand-alone functions to be called by both ChannelList and Int64Array.
An anonymous (or embedded field) is found in a struct (see struct type), not in a type alias (or "type declaration").
You cannot embed a type declaration within another type declaration.
Plus, as illustrated by the answers to "Go: using a pointer to array", you shouldn't be using pointers to slice, use directly the slice themselves (passed by value).
Wessie kindly points out in the comments that (ia *Int64Array) Scan() uses pointer to a slice in order to mutate the underlying array referenced by said slice.
I would prefer returning another slice instead of mutating the existing one.
That being said, the Golang Code Review does mention:
If the receiver is a struct, array or slice and any of its elements is a pointer to something that might be mutating, prefer a pointer receiver, as it will make the intention more clear to the reader.

Resources