I have a map: []map[string]string.
Populating the results in a json.marshal() compatible object. Outputing:
[
{
"key1": "val1",
"key2": "val2"
},
{
"randval3": "val1",
"randval2": "xyz1"
"randval1": "xyz3"
},
...
]
However, when I run xml.marshal(). I receive a xml: unsupported type: map[string]string. Which seems reasonable given the fact that XML needs node names etc. So what I'm basically looking for is a way to get:
<rootElement>
<child>
<key1>val1</key1>
<key2>val1</key2>
</child>
<child>
<randval3>val1</randval3>
<randval2>xyz1</randval2>
<randval1>xyz1</randval1>
</child>
</rootElement>
But I'm getting stuck with getting an 'object' compatible with xml.unmarshal()
You could declare a custom map and have it implement the xml.Marshaler interface.
type mymap map[string]string
func (m mymap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if err := e.EncodeToken(start); err != nil {
return err
}
for key, val := range m {
s := xml.StartElement{Name: xml.Name{Local: key}}
if err := e.EncodeElement(val, s); err != nil {
return err
}
}
return e.EncodeToken(start.End())
}
type RootElement struct {
XMLName xml.Name `xml:"rootElement"`
Children []mymap `xml:"child"`
}
https://play.golang.com/p/0_qA9UUvhKV
func main() {
root := RootElement{Children: []mymap{
{"key1": "val1", "key2": "val2"},
{"randval1": "val1", "randval2": "xyz1", "randval3": "abc3"},
}}
data, err := xml.MarshalIndent(root, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
output:
<rootElement>
<child>
<key2>val2</key2>
<key1>val1</key1>
</child>
<child>
<randval3>abc3</randval3>
<randval1>val1</randval1>
<randval2>xyz1</randval2>
</child>
</rootElement>
For Marshalling/Unmarshalling maps you need to write your own Marshal() and Unmarshal() functions.
Below an example for implementing these functions for type Maps []map[string]string and how to use them.
type Maps []map[string]string
type xmlMapEntry struct {
XMLName xml.Name
Value string `xml:",chardata"`
}
func (m Maps) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if len(m) == 0 {
return nil
}
err := e.EncodeToken(start)
if err != nil {
return err
}
for _, ma := range m {
for k, v := range ma {
e.Encode(xmlMapEntry{XMLName: xml.Name{Local: k}, Value: v})
}
}
return e.EncodeToken(start.End())
}
func (m *Maps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*m = Maps{}
for {
var e xmlMapEntry
err := d.Decode(&e)
if err == io.EOF {
break
} else if err != nil {
return err
}
*m = append(*m, map[string]string{e.XMLName.Local: e.Value})
}
return nil
}
func main() {
myarraymap := []map[string]string{
0: {"Key1": "val1"},
1: {"Key2": "val2"},
2: {"Randval3": "val1"},
3: {"Randval2": "xyz1"},
4: {"Randval1": "xyz2"},
}
// Encode to XML
x, _ := xml.MarshalIndent(Maps(myarraymap), "", " ")
fmt.Printf("my marshaled xml map: %v\n", string(x))
// Decode back from XML
var rm []map[string]string
xml.Unmarshal(x, (*Maps)(&rm))
fmt.Printf("my unmarshalled xml map: %v \n", rm)
}
Related
I'm trying to take an array of strings that I receive from a Go API, and write them to a file in a weird json list format. There are not brackets [], so i have to create a "dimension" for each of the string values in the array. I'm trying to do this using types/structs, but i keep getting stuck (new at Go). Should i try and just use maps, or is there a way to accomplish this?
This is the code I am using right now:
package main
import (
"fmt"
"io/ioutil"
)
type Dimension struct {
SQL, Type string
}
type Measure struct {
Type string
DrillMembers []string
}
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
a := []string{"country", "year", "gdpPercap", "lifeExp", "pop", "continent"}
cubeSchema := "measures: {\n count: {\n type: `count`,\n drillMembers: "
for i, s := range a {
cubeSchema += s
fmt.Println(cubeSchema)
fmt.Println(i)
}
fileText := []byte(cubeSchema)
fmt.Println(cubeSchema)
err := ioutil.WriteFile("test.js", fileText, 0644)
check(err)
}
This is how I need the output to look:
measures: {
count: {
type: `count`,
drillMembers: [country]
}
},
dimensions: {
country: {
sql: `country`,
type: `string`
},
year: {
sql: `year`,
type: `string`
},
gdppercap: {
sql: `gdppercap`,
type: `string`
},
lifeexp: {
sql: `lifeexp`,
type: `string`
},
pop: {
sql: `pop`,
type: `string`
},
continent: {
sql: `continent`,
type: `string`
}
}
Right now I keep getting stuck with the following output:
measures: {
count: {
type: `count`,
drillMembers: countryyeargdpPercaplifeExppopcontinent
package main
import (
"fmt"
"io/ioutil"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
schema := `measures: {
count: {
type: 'count',
drillMembers: [country]
}
},
dimensions: {
`
a := []string{"country", "year", "gdpPercap", "lifeExp", "pop", "continent"}
var cubeSchema string
for _, s := range a {
cubeSchema += s + ": {\n\tsql: " + s + ",\n\ttype: `string`\n},\n"
}
fileText := []byte(schema + cubeSchema + "}\n}")
fmt.Println(cubeSchema)
err := ioutil.WriteFile("test.js", fileText, 0644)
check(err)
}
check this code.
I tried to do the second part:
package main
import (
"encoding/json"
"fmt"
)
func main() {
a := []string{"country", "year", "gdpPercap", "lifeExp", "pop", "continent"}
var items map[string]sqlType
items = make(map[string]sqlType)
for _, v := range a {
items[v] = sqlType{SQL: v, Type: "string"}
}
dimensions := dimensions{Dimensions: items}
bytes, err := json.Marshal(dimensions)
if err != nil {
panic(err)
}
c := string(bytes)
fmt.Println(c)
}
type sqlType struct {
SQL string `json:"sql"`
Type string `json:"type"`
}
type dimensions struct {
Dimensions map[string]sqlType `json:"dimensions"`
}
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
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.