Is there a way to convert struct to array of values in Golang?
for example if I have this kind of struct (not just this one):
type Model struct {
Id bson.ObjectId `bson:"_id,omitempty"`
CreatedAt time.Time `bson:",omitempty"`
UpdatedAt time.Time `bson:",omitempty"`
DeletedAt time.Time `bson:",omitempty"`
CreatedBy bson.ObjectId `bson:",omitempty"`
UpdatedBy bson.ObjectId `bson:",omitempty"`
DeletedBy bson.ObjectId `bson:",omitempty"`
Logs []bson.ObjectId `bson:",omitempty"`
}
type User struct {
Name string `bson:"name"`
Model `bson:",inline"`
}
The case was, I usually send the JSON to the browser with this format:
var iota = -1
var data = {
NAME: ++iota, ID: ++iota, CREATED_AT: ++iota, UPDATED_AT: ++iota, DELETED_AT: ++iota, // and so on
rows: [['kiz',1,'2014-01-01','2014-01-01','2014-01-01'],
['yui',2,'2014-01-01','2014-01-01','2014-01-01'],
['ham',3,'2014-01-01','2014-01-01','2014-01-01'] // and so on
]
};
Instead of:
var data = {
rows: [{NAME:'kiz',ID:1,CreatedAt:'2014-01-01',UpdatedAt:'2014-01-01',DeletedAt:'2014-01-01'},
{NAME:'yui',ID:2,CreatedAt:'2014-01-01',UpdatedAt:'2014-01-01',DeletedAt:'2014-01-01'},
{NAME:'ham',ID:3,CreatedAt:'2014-01-01',UpdatedAt:'2014-01-01',DeletedAt:'2014-01-01'} // and so on
]
}
Here's what I've tried:
import (
"github.com/kr/pretty"
//"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"reflect"
"runtime"
"strings"
"time"
)
// copy the model from above
func Explain(variable interface{}) {
_, file, line, _ := runtime.Caller(1)
//res, _ := json.MarshalIndent(variable, " ", " ")
res := pretty.Formatter(variable)
fmt.Printf("%s:%d: %# v
", file[len(FILE_PATH):], line, res)
//spew.Dump(variable)
}
func s2a(i interface{}) []interface{} { // taken from https://gist.github.com/tonyhb/5819315
iVal := reflect.ValueOf(i).Elem()
//typ := iVal.Type()
values := make([]interface{}, 0, iVal.NumField())
for i := 0; i < iVal.NumField(); i++ {
f := iVal.Field(i)
//tag := typ.Field(i).Tag.Get("tagname")
//fmt.Println(tag)
// name := typ.Field(i).Name
v := f.Interface()
switch v.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, string, []byte, time.Time:
// do nothing
// case struct{}: // how to catch any embeeded struct?
case Model: // Model (or any embedded/nameless struct) should also converted to array
//arr := s2a() // invalid type assertion: f.(Model) (non-interface type reflect.Value on left)
//arr := s2a(f.Addr().(&Model)) // invalid type assertion: f.Addr().(&Model) (non-interface type reflect.Value on left)
// umm.. how to convert f back to Model?
//for _, e := range arr {
values = append(values, e)
//}
default: // struct? but also interface and map T_T
//v = s2a(&v)
}
values = append(values, v)
}
return values
}
func main() {
//sess, err := mgo.Dial("127.0.0.1")
//Check(err, "unable to connect")
//db := sess.DB("test")
//coll := db.C("coll1")
user := User{}
user.Id = bson.NewObjectId()
user.Name = "kis"
//changeInfo, err := coll.UpsertId(user.Id, user)
//Check(err, "failed to insert")
//Explain(changeInfo)
//Explain(s2a(changeInfo))
user.Name = "test"
Explain(user)
Explain(s2a(&user))
//err = coll.FindId(user.Id).One(&user)
//Check(err, "failed to fetch")
//Explain(user)
//Explain(s2a(&user))
user.CreatedAt = time.Now()
//err = coll.UpdateId(user.Id, user)
//Check(err, "failed to update")
Explain(changeInfo)
Explain(s2a(&user))
user.CreatedAt = user.DeletedAt
//err = coll.FindId(user.Id).One(&user)
//Check(err, "failed to fetch")
Explain(user)
Explain(s2a(&user))
}
Is there easy/fast way to convert struct to array (and if there struct embedded/inside it, converted to array also)?
Why not use reflect.Kind()? Here's the playground: http://play.golang.org/p/YjbsnB4eln
Use the reflect package.
Here's some playground code that'll work for one record (of any struct type), you can refactor it to work for a slice of records.
EDIT: (copy-pasted for good measure)
package main
import "fmt"
import "strings"
import "reflect"
type X struct {
Y string
Z int
}
func main() {
data := X{"yval",3}
expectedResult := `{"Y": 0, "Z": 1, "rows": [["yval", 3]]}`
fmt.Println(convert(data))
fmt.Println(expectedResult)
}
func convert(data interface{}) string {
v := reflect.ValueOf(data)
n := v.NumField()
st := reflect.TypeOf(data)
headers := make([]string, n)
for i := 0; i < n; i++ {
headers[i] = fmt.Sprintf(`"%s": %d`, st.Field(i).Name, i)
}
rowContents := make([]string, n)
for i := 0; i < n; i++ {
x := v.Field(i)
s := fmt.Sprintf("%v", x.Interface())
if x.Type().String() == "string" {
s = `"` + s + `"`
}
rowContents[i] = s
}
return "{" + strings.Join(headers, ", ") + `, "rows": [[` + strings.Join(rowContents, ", ") + "]]}"
}
If you are happy to specify a fixed order for the fields in the array representation, you could do this by implementing the json.Marshaler
interface to customise its representation. For example:
func (u User) MarshalJSON() ([]byte, error) {
a := []interface{}{
u.Name,
u.Id,
...,
}
return json.Marshal(a)
}
Now when you marshal variables of this type, they will be represented as an array. If you want to also do the reverse (unmarshal an array into this struct), you will also need to implement the json.Unmarshaler
interface. This could be done in a similar fashion, using json.Unmarshal
to decode into a []interface{}
slice and then pull out the values. Make sure UnmarshalJSON
is declared to take a pointer receiver though, or your code won't work (you'll end up updating a copy of the struct rather than the struct itself).