I found some posts on how to decoding json nested objects in go, I tried to apply the answers to my problem, but I only managed to find a partial solution.
My json file look like this:
{
"user":{
"gender":"male",
"age":"21-30",
"id":"80b1ea88-19d7-24e8-52cc-65cf6fb9b380"
},
"trials":{
"0":{"index":0,"word":"WORD 1","Time":3000,"keyboard":true,"train":true,"type":"A"},
"1":{"index":1,"word":"WORD 2","Time":3000,"keyboard":true,"train":true,"type":"A"},
},
"answers":{
"training":[
{"ans":0,"RT":null,"gtAns":"WORD 1","correct":0},
{"ans":0,"RT":null,"gtAns":"WORD 2","correct":0}
],
"test":[
{"ans":0,"RT":null,"gtAns":true,"correct":0},
{"ans":0,"RT":null,"gtAns":true,"correct":0}
]
}
}
Basically I need to parse the information inside it and save them into go structure. With the code below I managed to extract the user information, but it looks too complicated to me and it won't be easy to apply the same thing to the "answers" fields which contains 2 arrays with more than 100 entries each. Here the code I'm using now:
type userDetails struct {
Id string `json:"id"`
Age string `json:"age"`
Gender string `json:"gender"`
}
type jsonRawData map[string]interface {
}
func getJsonContent(r *http.Request) ( userDetails) {
defer r.Body.Close()
jsonBody, err := ioutil.ReadAll(r.Body)
var userDataCurr userDetails
if err != nil {
log.Printf("Couldn't read request body: %s", err)
} else {
var f jsonRawData
err := json.Unmarshal(jsonBody, &f)
if err != nil {
log.Printf("Error unmashalling: %s", err)
} else {
user := f["user"].(map[string]interface{})
userDataCurr.Id = user["id"].(string)
userDataCurr.Gender = user["gender"].(string)
userDataCurr.Age = user["age"].(string)
}
}
return userDataCurr
}
Any suggestions? Thanks a lot!
You're doing it the hard way by using interface{} and not taking advantage of what encoding/json gives you.
I'd do it something like this (note I assumed there was an error with the type of the "gtAns" field and I made it a boolean, you don't give enough information to know what to do with the "RT" field):
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"strconv"
"strings"
)
const input = `{
"user":{
"gender":"male",
"age":"21-30",
"id":"80b1ea88-19d7-24e8-52cc-65cf6fb9b380"
},
"trials":{
"0":{"index":0,"word":"WORD 1","Time":3000,"keyboard":true,"train":true,"type":"A"},
"1":{"index":1,"word":"WORD 2","Time":3000,"keyboard":true,"train":true,"type":"A"}
},
"answers":{
"training":[
{"ans":0,"RT":null,"gtAns":true,"correct":0},
{"ans":0,"RT":null,"gtAns":true,"correct":0}
],
"test":[
{"ans":0,"RT":null,"gtAns":true,"correct":0},
{"ans":0,"RT":null,"gtAns":true,"correct":0}
]
}
}`
type Whatever struct {
User struct {
Gender Gender `json:"gender"`
Age Range `json:"age"`
ID IDString `json:"id"`
} `json:"user"`
Trials map[string]struct {
Index int `json:"index"`
Word string `json:"word"`
Time int // should this be a time.Duration?
Train bool `json:"train"`
Type string `json:"type"`
} `json:"trials"`
Answers map[string][]struct {
Answer int `json:"ans"`
RT json.RawMessage // ??? what type is this
GotAnswer bool `json:"gtAns"`
Correct int `json:"correct"`
} `json:"answers"`
}
// Using some custom types to show custom marshalling:
type IDString string // TODO custom unmarshal and format/error checking
type Gender int
const (
Male Gender = iota
Female
)
func (g *Gender) UnmarshalJSON(b []byte) error {
var s string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
switch strings.ToLower(s) {
case "male":
*g = Male
case "female":
*g = Female
default:
return fmt.Errorf("invalid gender %q", s)
}
return nil
}
func (g Gender) MarshalJSON() ([]byte, error) {
switch g {
case Male:
return []byte(`"male"`), nil
case Female:
return []byte(`"female"`), nil
default:
return nil, fmt.Errorf("invalid gender %v", g)
}
}
type Range struct{ Min, Max int }
func (r *Range) UnmarshalJSON(b []byte) error {
// XXX could be improved
_, err := fmt.Sscanf(string(b), `"%d-%d"`, &r.Min, &r.Max)
return err
}
func (r Range) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%d-%d"`, r.Min, r.Max)), nil
// Or:
b := make([]byte, 0, 8)
b = append(b, '"')
b = strconv.AppendInt(b, int64(r.Min), 10)
b = append(b, '-')
b = strconv.AppendInt(b, int64(r.Max), 10)
b = append(b, '"')
return b, nil
}
func fromJSON(r io.Reader) (Whatever, error) {
var x Whatever
dec := json.NewDecoder(r)
err := dec.Decode(&x)
return x, err
}
func main() {
// Use http.Get or whatever to get an io.Reader,
// (e.g. response.Body).
// For playground, substitute a fixed string
r := strings.NewReader(input)
// If you actually had a string or []byte:
// var x Whatever
// err := json.Unmarshal([]byte(input), &x)
x, err := fromJSON(r)
if err != nil {
log.Fatal(err)
}
fmt.Println(x)
fmt.Printf("%+v\n", x)
b, err := json.MarshalIndent(x, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Re-marshalled: %s\n", b)
}
Playground
Of course if you want to reuse those sub-types you could pull them out of the "Whatever" type into their own named types.
Also, note the use of a json.Decoder rather than reading in all the data ahead of time. Usually try and avoid any use of ioutil.ReadAll unless you really need all the data at once.
Related
I have an problem with decoding an JSON respone. I've tried to solve this problem for a couple off weeks and can't find an working solution online.
This is my Go code that gets the response:
package main
import (
"fmt"
"time"
"strconv"
"encoding/json"
"net/http"
"io"
)
const (
binanceUrl_0 = "https://api.binance.com"
binanceUrl_1 = "https://api1.binance.com"
binanceUrl_2 = "https://api2.binance.com"
binanceUrl_3 = "https://api3.binance.com"
//select which url to use
binanceUrl = binanceUrl_0
binance_GetServerTime = binanceUrl + "/api/v3/time"
binance_Ping = binanceUrl + "/api/v3/ping"
binance_GetExhangeInfo = binanceUrl + "/api/v3/exchangeInfo"
binance_GetExhangeInfo_Symbol = binanceUrl + "/api/v3/exchangeInfo?symbol=BNBBTC"
binance_GetKlineData = binanceUrl + "/api/v1/klines"
)
type Binance_klines struct {
OpenTime int64
open float32
high float32
low float32
close float32
volume float32
CloseTime int64
QuoteVolume float32
NumTrades int64
TakerBaseVolume float32
TakerQuoteVolume float32
}
func GetKlines_wEndTime(symbol string, interval string, limit int, endTime time.Time) ([]Binance_klines, error) {
var url string
url = binance_GetKlineData + "?" +
"symbol=" + symbol +
"&interval=" + interval +
"&limit=" + strconv.FormatInt(int64(limit), 10) +
"&endTime=" + strconv.FormatInt(endTime.Unix(),10 ) + "000"
response, err := http.Get(url)
if err != nil {
return nil, err
}
data, err := respToKlines(response.Body)
if err != nil {
return nil, err
}
response.Body.Close()
return data, nil
}
func respToKlines(data io.Reader) ([]Binance_klines, error) {
var klines []Binance_klines
var decoded []interface{}
decoder := json.NewDecoder(data)
err := decoder.Decode(&decoded)
if err != nil {
return nil, err
}
//Attempt 1:
//kline = (decoded).([]Binance_klines)
//err: invalid operation: (decoded) (variable of type []interface{}) is not an interface
//Attempt 2:
for i:=0; i<len(decoded); i++ {
to_parse := decoded[i]
fmt.Println("to_parse",to_parse)
//prints: to_parse [1.66427838e+12 20982.91000000 20992.61000000 20977.90000000 20980.95000000 0.68063000 1.664278439999e+12 14282.75833530 57 0.27942000 5864.01792110 0]
var kline Binance_klines
kline = (to_parse).(Binance_klines)
//err: interface conversion: interface {} is []interface {}, not dsBinance.Binance_klines
fmt.Println("kline",kline)
}
return klines, nil
}
func main() {
result, err := GetKlines_wEndTime( "BTCEUR", "1m", 3, time.Now() )
fmt.Println(result, err)
}
This is an reponse I get (converted to string from bytes):
[[1664277480000,"20980.42000000","20984.06000000","20966.57000000","20970.14000000","6.10441000",1664277539999,"128041.93403330",142,"2.97844000","62486.29173860","0"],[1664277540000,"20969.14000000","20976.08000000","20955.69000000","20970.15000000","3.17365000",1664277599999,"66548.64583140",88,"2.39827000","50292.47196580","0"],[1664277600000,"20970.15000000","20970.15000000","20970.15000000","20970.15000000","0.00000000",1664277659999,"0.00000000",0,"0.00000000","0.00000000","0"]]
My question is, what am I doing wrong? When I use a tool like https://mholt.github.io/json-to-go/, it wants me to make an [][]interface{}. But in my for loop you can see that it prints an (in my eyes) a valid: []interface{} but i cannot convert it to an struct of type Binance_Klines.
Is something wrong with this line:
kline = (to_parse).(Binance_klines)
Or am I just misunderstanding something? What do I need to change to be able to use the type assertion? Or to just decode it at once to the right struct?
You cannot cast []interface{} to Binance_klines. So kline = (to_parse).(Binance_klines) fails. You have to write the translation yourself.
The data returned is a 2 dimensional array. Here is the json payload you have formatted. The types are a mix of string and float64, so Go uses interface{} to store the values.
[
[
1664277480000,
"20980.42000000",
"20984.06000000",
"20966.57000000",
"20970.14000000",
"6.10441000",
1664277539999,
"128041.93403330",
142,
"2.97844000",
"62486.29173860",
"0"
],
[
1664277540000,
"20969.14000000",
"20976.08000000",
"20955.69000000",
"20970.15000000",
"3.17365000",
1664277599999,
"66548.64583140",
88,
"2.39827000",
"50292.47196580",
"0"
],
[
1664277600000,
"20970.15000000",
"20970.15000000",
"20970.15000000",
"20970.15000000",
"0.00000000",
1664277659999,
"0.00000000",
0,
"0.00000000",
"0.00000000",
"0"
]
]
The json decoder cannot convert this into your Binance_klines struct. But you can override the default unmarshal behavior yourself.
First I made a type for the sometimes quoted numbers, sometimes not.
type BinanceNumber string
func (b *BinanceNumber) UnmarshalJSON(data []byte) error {
*b = BinanceNumber(strings.Trim(string(data), "\""))
return nil
}
func (b BinanceNumber) Float64() float64 {
f, err := strconv.ParseFloat(string(b), 64)
if err != nil {
panic(err)
}
return f
}
func (b BinanceNumber) Int64() int64 {
i, err := strconv.ParseInt(string(b), 10, 64)
if err != nil {
panic(err)
}
return i
}
Then you override the Binance_klines unmarshal.
func (b *Binance_klines) UnmarshalJSON(data []byte) error {
var array []BinanceNumber
err := json.Unmarshal(data, &array)
if err != nil {
return err
}
b.OpenTime = array[0].Int64()
b.Open = float32(array[1].Float64())
b.High = float32(array[2].Float64())
b.Low = float32(array[3].Float64())
b.Close = float32(array[4].Float64())
b.Volume = float32(array[5].Float64())
b.CloseTime = array[6].Int64()
b.QuoteVolume = float32(array[7].Float64())
b.NumTrades = array[8].Int64()
b.TakerBaseVolume = float32(array[9].Float64())
b.TakerQuoteVolume = float32(array[10].Float64())
return nil
}
Putting it all together: https://go.dev/play/p/SGGbWEUFxJr.
This part:
var kline Binance_klines
kline = (to_parse).(Binance_klines)
needs to become
var kline Binance_klines
kline.OpenTimer = to_parse[0].(int64)
kline.open = strconv.ParseFloat(to_parse[1].(string), 64)
...
You are not receiving the json representation of your Binance_klines struct but a slice of any (a mix or numbers and strings).
What I'm trying to do:
Transform all arrays of length 1 in a JSON file to non arrays.
e.g.
Input: {"path": [{"secret/foo": [{"capabilities": ["read"]}]}]}
Output: {"path": {"secret/foo": {"capabilities": "read"}}}
I can't use Structs as the JSON format will vary...
Right now I've managed to at least detect the 1 length slices:
package main
import (
"encoding/json"
"fmt"
)
func findSingletons(value interface{}) {
switch value.(type) {
case []interface{}:
if len(value.([]interface{})) == 1 {
fmt.Println("1 length array found!", value)
}
for _, v := range value.([]interface{}) {
findSingletons(v)
}
case map[string]interface{}:
for _, v := range value.(map[string]interface{}) {
findSingletons(v)
}
}
}
func removeSingletonsFromJSON(input string) {
jsonFromInput := json.RawMessage(input)
jsonMap := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonFromInput), &jsonMap)
if err != nil {
panic(err)
}
findSingletons(jsonMap)
fmt.Printf("JSON value of without singletons:%s\n", jsonMap)
}
func main() {
jsonParsed := []byte(`{"path": [{"secret/foo": [{"capabilities": ["read"]}]}]}`)
removeSingletonsFromJSON(string(jsonParsed))
fmt.Println(`Should have output {"path": {"secret/foo": {"capabilities": "read"}}}`)
}
Which outputs
1 length array found! [map[secret/foo:[map[capabilities:[read]]]]]
1 length array found! [map[capabilities:[read]]]
1 length array found! [read]
JSON value of without singletons:map[path:[map[secret/foo:[map[capabilities:[read]]]]]]
Should have output {"path": {"secret/foo": {"capabilities": "read"}}}
But I'm not sure how I can change them into non-arrays...
The type switch is your friend:
switch t := v.(type) {
case []interface{}:
if len(t) == 1 {
data[k] = t[0]
And you may use recursion to remove inside elements, like so:
func removeOneElementSlice(data map[string]interface{}) {
for k, v := range data {
switch t := v.(type) {
case []interface{}:
if len(t) == 1 {
data[k] = t[0]
if v, ok := data[k].(map[string]interface{}); ok {
removeOneElementSlice(v)
}
}
}
}
}
I would do this to convert
{"path":[{"secret/foo":[{"capabilities":["read"]}]}]}
to
{"path":{"secret/foo":{"capabilities":"read"}}}:
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
s := `{"path":[{"secret/foo":[{"capabilities":["read"]}]}]}`
fmt.Println(s)
var data map[string]interface{}
if err := json.Unmarshal([]byte(s), &data); err != nil {
panic(err)
}
removeOneElementSlice(data)
buf, err := json.Marshal(data)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf)) //{"a":"a","n":7}
}
func removeOneElementSlice(data map[string]interface{}) {
for k, v := range data {
switch t := v.(type) {
case []interface{}:
if len(t) == 1 {
data[k] = t[0]
if v, ok := data[k].(map[string]interface{}); ok {
removeOneElementSlice(v)
}
}
}
}
}
I am trying to write a program that is supposed to fetch a bunch of data from an RSS link (that part works), and then store it in a GUI window. However, my code shows the GUI window for about a millisecond, then gives me this error:
panic: runtime error: index out of range
goroutine 1 [running, locked to thread]:
main.(*modelHandler).CellValue(0xc0000e81b0, 0xc0000760e0, 0x0, 0x0, 0x8, 0xc00004c380)
C:/Users/Owner/go/src/FinalProject/GUI-Based System.go:71 +0x517
github.com/andlabs/ui.pkguiDoTableModelCellValue(0x9ba820, 0x17ba1b0, 0x0, 0x4047a0)
C:/Users/Owner/go/src/github.com/andlabs/ui/tablemodel.go:171 +0xc3
github.com/andlabs/ui._cgoexpwrap_7d4d3b498194_pkguiDoTableModelCellValue(0x9ba820, 0x17ba1b0, 0x0, 0x0)
_cgo_gotypes.go:4087 +0x7f
github.com/andlabs/ui._Cfunc_uiControlShow(0x3977350)
_cgo_gotypes.go:1624 +0x48
github.com/andlabs/ui.(*ControlBase).Show.func1(0x3977350)
C:/Users/Owner/go/src/github.com/andlabs/ui/control.go:108 +0x5d
github.com/andlabs/ui.(*ControlBase).Show(0xc000394380)
C:/Users/Owner/go/src/github.com/andlabs/ui/control.go:108 +0x36
main.setupUI()
C:/Users/Owner/go/src/FinalProject/GUI-Based System.go:131 +0x2b2
github.com/andlabs/ui.pkguiDoQueueMain(0x0)
C:/Users/Owner/go/src/github.com/andlabs/ui/main.go:103 +0xb0
github.com/andlabs/ui._cgoexpwrap_7d4d3b498194_pkguiDoQueueMain(0x0)
_cgo_gotypes.go:3967 +0x32
github.com/andlabs/ui._Cfunc_uiMain()
_cgo_gotypes.go:2519 +0x48
github.com/andlabs/ui.Main(0x8037a8, 0x77e420, 0xc000048920)
C:/Users/Owner/go/src/github.com/andlabs/ui/main.go:41 +0x104
main.main()
C:/Users/Owner/go/src/FinalProject/GUI-Based System.go:151 +0x1b7
Process finished with exit code 2
This is the code I have written so far:
package main
import (
"encoding/xml"
"github.com/andlabs/ui"
_ "github.com/andlabs/ui/winmanifest"
"log"
"net/http"
"regexp"
"strings"
)
type JobInfo struct{
Title string `xml:"title"`
Location string `xml:"location"`
PostDate string `xml:"pubDate"`
Description string `xml:"description"`
}
type Channel struct{
Title string `xml:"title"`
Link string `xml:"link"`
Desc string `xml:"description"`
Items []JobInfo `xml:"item"`
}
type Rss struct {
Channel []Channel `xml:"channel"`
}
type modelHandler struct{
row9Text string
yellowRow int
checkStates [10000]int
}
func replace_weird_characters(s string) string{
reg, err := regexp.Compile(" |<[a-zA-Z /]+>")
if err != nil{
log.Fatal(err)
}
new_string := string(reg.ReplaceAllString(s, " "))
return new_string
}
func newModelHandler() *modelHandler {
m := new(modelHandler)
m.row9Text = "You can edit this one"
m.yellowRow = -1
return m
}
func (mh *modelHandler) ColumnTypes(m *ui.TableModel) []ui.TableValue {
return []ui.TableValue{
ui.TableString(""),
ui.TableString(""),
ui.TableString(""),
ui.TableString(""),
ui.TableString(""),
ui.TableColor{},
ui.TableColor{},
}
}
func (mh *modelHandler) NumRows(m *ui.TableModel) int {
return 10000
}
func (mh *modelHandler) CellValue(m *ui.TableModel, row, column int) ui.TableValue {
new_rss := Channel{}
if column == 0{
return ui.TableString(new_rss.Items[row].Title[:strings.LastIndex(new_rss.Items[row].Title," at ")])
}
if column == 1{
return ui.TableString(new_rss.Items[row].Location)
}
if column == 2 {
company := new_rss.Items[row].Title[strings.LastIndex(new_rss.Items[row].Title, " at "):strings.LastIndex(new_rss.Items[row].Title, " (")]
real_company_name := strings.Replace(company, " at ", "", -1)
return ui.TableString(real_company_name)
}
if column == 3 {
postdate := strings.Replace(new_rss.Items[row].PostDate, "Z", "", -1)
return ui.TableString(postdate)
}
if column == 4 {
real_description := replace_weird_characters(new_rss.Items[row].Description)
return ui.TableString(real_description)
}
panic("unreachable")
}
func (mh *modelHandler) SetCellValue(m *ui.TableModel, row, column int, value ui.TableValue) {
if row == 9 && column == 2 {
mh.row9Text = string(value.(ui.TableString))
}
}
func setupUI() {
mainwin := ui.NewWindow("something", 640, 480, true)
mainwin.OnClosing(func(*ui.Window) bool {
ui.Quit()
return true
})
ui.OnShouldQuit(func() bool {
mainwin.Destroy()
return true
})
mh := newModelHandler()
model := ui.NewTableModel(mh)
table := ui.NewTable(&ui.TableParams{
Model: model,
})
mainwin.SetChild(table)
mainwin.SetMargined(true)
table.AppendTextColumn("Title",
0, ui.TableModelColumnNeverEditable, nil)
table.AppendTextColumn("Location",
1, ui.TableModelColumnNeverEditable, nil)
table.AppendTextColumn("Company",
2, ui.TableModelColumnNeverEditable, nil)
table.AppendTextColumn("Post Date",
3, ui.TableModelColumnNeverEditable, nil)
table.AppendTextColumn("Description",
4, ui.TableModelColumnNeverEditable, nil)
mainwin.Show()
}
func main() {
resp, err := http.Get("https://stackoverflow.com/jobs/feed?l=Bridgewater%2c+MA%2c+USA&u=Miles&d=100")
if err != nil{
log.Fatal(err)
return
}
defer resp.Body.Close()
rss := Rss{}
decoder := xml.NewDecoder(resp.Body)
err = decoder.Decode(&rss)
if err != nil{
log.Fatal(err)
return
}
ui.Main(setupUI)
}
I have determined that it results in an error when I'm trying to do things like
new_rss.Items[row].Location
However, I'm not sure how to turn it back into a regular string.
You are calling .Items[row].Title (and others) on a brand new Channel struct - something in that code is incorrect, as any row value here will cause an error like that...
(As the Items are uninitialised)
I'm trying to unmarshal a JSON array of the following type:
[
{"abc's": "n;05881364"},
{"abcoulomb": "n;13658345"},
{"abcs": "n;05881364"}
]
into a map[string]string. This question Golang parse JSON array into data structure almost answered my problem, but mine is a truly map, not an array of maps. Unmarshaling into a []map[string]string worked but I now get a map of map[string]string, not a simple map of string as it should be
There is no way to do it directly with the json package; you have to do the conversion yourself. This is simple:
package main
import (
"encoding/json"
"fmt"
)
func main() {
data := []byte(`
[
{"abc's": "n;05881364"},
{"abcoulomb": "n;13658345"},
{"abcs": "n;05881364"}
]
`)
var mapSlice []map[string]string
if err := json.Unmarshal(data, &mapSlice); err != nil {
panic(err)
}
resultingMap := map[string]string{}
for _, m := range mapSlice {
for k, v := range m {
resultingMap[k] = v
}
}
fmt.Println(resultingMap)
}
Output
map[abc's:n;05881364 abcoulomb:n;13658345 abcs:n;05881364]
An alternative (though very similar) to Alex's answer is to define your own type along with an UnmarshalJSON function.
package main
import (
"encoding/json"
"fmt"
)
type myMapping map[string]string
func (mm myMapping) UnmarshalJSON(b []byte) error {
var temp []map[string]string
if err := json.Unmarshal(b, &temp); err != nil {
return err
}
for _, m := range temp {
for k, v := range m {
mm[k] = v
}
}
return nil
}
func main() {
data := []byte(`
[
{"abc's": "n;05881364"},
{"abcoulomb": "n;13658345"},
{"abcs": "n;05881364"}
]`)
resultingMap := myMapping{}
if err := json.Unmarshal(data, &resultingMap); err != nil {
panic(err)
}
fmt.Println(resultingMap)
}
Playground
Basically after doing a query I'd like to take the resulting rows and produce a []map[string]interface{}, but I do not see how to do this with the API since the Rows.Scan() function needs a specific number of parameters matching the requested number of columns (and possibly the types as well) to correctly obtain the data.
Again, I'd like to generalize this call and take any query and turn it into a []map[string]interface{}, where the map contains column names mapped to the values for that row.
This is likely very inefficient, and I plan on changing the structure later so that interface{} is a struct for a single data point.
How would I do this using just the database/sql package, or if necessary the database/sql/driver package?
Look at using sqlx, which can do this a little more easily than the standard database/sql library:
places := []Place{}
err := db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
if err != nil {
fmt.Printf(err)
return
}
You could obviously replace []Place{} with a []map[string]interface{}, but where possible it is better to use a struct if you know the structure of your database. You won't need to undertake any type assertions as you might on an interface{}.
I haven't used it (yet), but I believe the "common" way to do what you are asking (more or less) is to use gorp.
You can create a struct that maintains the map key to the position of the []interface{} slice. By doing this, you do not need to create a predefined struct. For example:
IDOrder: 0
IsClose: 1
IsConfirm: 2
IDUser: 3
Then, you can use it like this:
// create a fieldbinding object.
var fArr []string
fb := fieldbinding.NewFieldBinding()
if fArr, err = rs.Columns(); err != nil {
return nil, err
}
fb.PutFields(fArr)
//
outArr := []interface{}{}
for rs.Next() {
if err := rs.Scan(fb.GetFieldPtrArr()...); err != nil {
return nil, err
}
fmt.Printf("Row: %v, %v, %v, %s\n", fb.Get("IDOrder"), fb.Get("IsConfirm"), fb.Get("IDUser"), fb.Get("Created"))
outArr = append(outArr, fb.GetFieldArr())
}
Sample output:
Row: 1, 1, 1, 2016-07-15 10:39:37 +0000 UTC
Row: 2, 1, 11, 2016-07-15 10:42:04 +0000 UTC
Row: 3, 1, 10, 2016-07-15 10:46:20 +0000 UTC
SampleQuery: [{"Created":"2016-07-15T10:39:37Z","IDOrder":1,"IDUser":1,"IsClose":0,"IsConfirm":1},{"Created":"2016-07-15T10:42:04Z","IDOrder":2,"IDUser":11,"IsClose":0,"IsConfirm":1},{"Created":"2016-07-15T10:46:20Z","IDOrder":3,"IDUser":10,"IsClose":0,"IsConfirm":1}]
Please see the full example below or at fieldbinding:
main.go
package main
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
)
import (
_ "github.com/go-sql-driver/mysql"
"github.com/junhsieh/goexamples/fieldbinding/fieldbinding"
)
var (
db *sql.DB
)
// Table definition
// CREATE TABLE `salorder` (
// `IDOrder` int(10) unsigned NOT NULL AUTO_INCREMENT,
// `IsClose` tinyint(4) NOT NULL,
// `IsConfirm` tinyint(4) NOT NULL,
// `IDUser` int(11) NOT NULL,
// `Created` datetime NOT NULL,
// `Changed` datetime NOT NULL,
// PRIMARY KEY (`IDOrder`),
// KEY `IsClose` (`IsClose`)
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
func main() {
var err error
// starting database server
db, err = sql.Open("mysql", "Username:Password#tcp(Host:Port)/DBName?parseTime=true")
if err != nil {
panic(err.Error()) // Just for example purpose. You should use proper error handling instead of panic
}
defer db.Close()
// SampleQuery
if v, err := SampleQuery(); err != nil {
fmt.Printf("%s\n", err.Error())
} else {
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(v); err != nil {
fmt.Printf("SampleQuery: %v\n", err.Error())
}
fmt.Printf("SampleQuery: %v\n", b.String())
}
}
func SampleQuery() ([]interface{}, error) {
param := []interface{}{}
param = append(param, 1)
sql := "SELECT "
sql += " SalOrder.IDOrder "
sql += ", SalOrder.IsClose "
sql += ", SalOrder.IsConfirm "
sql += ", SalOrder.IDUser "
sql += ", SalOrder.Created "
sql += "FROM SalOrder "
sql += "WHERE "
sql += "IsConfirm = ? "
sql += "ORDER BY SalOrder.IDOrder ASC "
rs, err := db.Query(sql, param...)
if err != nil {
return nil, err
}
defer rs.Close()
// create a fieldbinding object.
var fArr []string
fb := fieldbinding.NewFieldBinding()
if fArr, err = rs.Columns(); err != nil {
return nil, err
}
fb.PutFields(fArr)
//
outArr := []interface{}{}
for rs.Next() {
if err := rs.Scan(fb.GetFieldPtrArr()...); err != nil {
return nil, err
}
fmt.Printf("Row: %v, %v, %v, %s\n", fb.Get("IDOrder"), fb.Get("IsConfirm"), fb.Get("IDUser"), fb.Get("Created"))
outArr = append(outArr, fb.GetFieldArr())
}
if err := rs.Err(); err != nil {
return nil, err
}
return outArr, nil
}
fieldbinding package:
package fieldbinding
import (
"sync"
)
// NewFieldBinding ...
func NewFieldBinding() *FieldBinding {
return &FieldBinding{}
}
// FieldBinding is deisgned for SQL rows.Scan() query.
type FieldBinding struct {
sync.RWMutex // embedded. see http://golang.org/ref/spec#Struct_types
FieldArr []interface{}
FieldPtrArr []interface{}
FieldCount int64
MapFieldToID map[string]int64
}
func (fb *FieldBinding) put(k string, v int64) {
fb.Lock()
defer fb.Unlock()
fb.MapFieldToID[k] = v
}
// Get ...
func (fb *FieldBinding) Get(k string) interface{} {
fb.RLock()
defer fb.RUnlock()
// TODO: check map key exist and fb.FieldArr boundary.
return fb.FieldArr[fb.MapFieldToID[k]]
}
// PutFields ...
func (fb *FieldBinding) PutFields(fArr []string) {
fCount := len(fArr)
fb.FieldArr = make([]interface{}, fCount)
fb.FieldPtrArr = make([]interface{}, fCount)
fb.MapFieldToID = make(map[string]int64, fCount)
for k, v := range fArr {
fb.FieldPtrArr[k] = &fb.FieldArr[k]
fb.put(v, int64(k))
}
}
// GetFieldPtrArr ...
func (fb *FieldBinding) GetFieldPtrArr() []interface{} {
return fb.FieldPtrArr
}
// GetFieldArr ...
func (fb *FieldBinding) GetFieldArr() map[string]interface{} {
m := make(map[string]interface{}, fb.FieldCount)
for k, v := range fb.MapFieldToID {
m[k] = fb.FieldArr[v]
}
return m
}
If you really want a map, which is needed in some cases, have a look at dbr, but you need to use the fork (since the pr got rejected in the original repo). The fork seems more up to date anyway:
https://github.com/mailru/dbr
For info on how to use it:
https://github.com/gocraft/dbr/issues/83
package main
import (
"fmt"
"github.com/bobby96333/goSqlHelper"
)
func main(){
fmt.Println("hello")
conn,err :=goSqlHelper.MysqlOpen("user:password#tcp(127.0.0.1:3306)/dbname")
checkErr(err)
row,err := conn.QueryRow("select * from table where col1 = ? and col2 = ?","123","abc")
checkErr(err)
if *row==nil {
fmt.Println("no found row")
}else{
fmt.Printf("%+v",row)
}
}
func checkErr(err error){
if err!=nil {
panic(err)
}
}
output:
&map[col1:abc col2:123]