New to Golang here and I'm trying to get a struct to convert to a JSON object that one of my other applications will consume.
The expected response will look something like this...
"access": {
"STOCK": "1",
"FOREX": "1",
"WEBFOREX": "1",
"WEBSTOCK": "1"
},
"subscription_group_dates": {
"32": {
"START_DATE": 1464753600,
"END_DATE": 1472616000
},
"42": {
"START_DATE": 1470024000,
"END_DATE": 1472616000
}
}
I have left in the "access" part of the object to show the parts I know work. Everything else has been omitted for brevity. The tough part for me is handling the nesting nature of structs in golang... The following Struct I think will work, but I'm not sure about the syntax.
Under the SubscriptionGroupDates Type... can I use map[string]struct and embed the rest as shown?. see below
type UCSUserAccess struct {
Groups map[string]string `json:"groups"`
Access map[string]string `json:"access"`
IsExpert string `json:"isExpert"`
SubscriptionGroupDates map[string]struct {
GroupID struct {
StartDate map[string]int `START_DATE`
EndDate map[string]int `END_DATE`
}
} `json:"subscription_group_dates"`
}
In addition... I have imported the "time" library in the import section. How would I use that to declare the dates as time objects? (instead of map[string]int)
Thanks
1- You may use Marshal
and Unmarshal
time, like this working sample code:
package main
import (
"encoding/json"
"fmt"
"log"
"strconv"
"time"
)
type UCSUserAccess struct {
Groups map[string]string `json:"groups"`
Access map[string]string `json:"access"`
IsExpert string `json:"isExpert"`
SubscriptionGroupDates map[string]struct {
StartDate Time `json:"START_DATE"`
EndDate Time `json:"END_DATE"`
} `json:"subscription_group_dates"`
}
type Time time.Time
func (t Time) MarshalJSON() ([]byte, error) {
data := []byte(fmt.Sprint(time.Time(t).UTC().Unix()))
return data, nil
}
func (t *Time) UnmarshalJSON(data []byte) error {
i, e := strconv.ParseInt(string(data), 10, 64)
*t = Time(time.Unix(i, 0).UTC())
return e
}
func (t Time) String() string {
return time.Time(t).UTC().String()
}
func main() {
str := `{
"access": {
"STOCK": "1",
"FOREX": "1",
"WEBFOREX": "1",
"WEBSTOCK": "1"
},
"subscription_group_dates": {
"32": {
"START_DATE": 1464753600,
"END_DATE": 1472616000
},
"42": {
"START_DATE": 1470024000,
"END_DATE": 1472616000
}
}
}`
var d UCSUserAccess
err := json.Unmarshal([]byte(str), &d)
if err != nil {
log.Fatal(err)
}
fmt.Println(d)
fmt.Println()
body, err := json.Marshal(d)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
2- You may use Marshal
and Unmarshal
time, like this simplified working sample code:
package main
import (
"encoding/json"
"fmt"
"log"
"strconv"
"time"
)
type UCSUserAccess struct {
StartDate Time `json:"START_DATE"`
}
type Time time.Time
func (t Time) MarshalJSON() ([]byte, error) {
data := []byte(fmt.Sprint(time.Time(t).UTC().Unix()))
return data, nil
}
func (t *Time) UnmarshalJSON(data []byte) error {
i, e := strconv.ParseInt(string(data), 10, 64)
*t = Time(time.Unix(i, 0).UTC())
return e
}
func (t Time) String() string {
return time.Time(t).UTC().String()
}
func main() {
str := `{
"START_DATE": 1464753600
}`
var d UCSUserAccess
err := json.Unmarshal([]byte(str), &d)
if err != nil {
log.Fatal(err)
}
fmt.Println(d)
fmt.Println()
body, err := json.Marshal(d)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
3- Also you may use int64
for time,like this working sample code:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
type GroupID struct {
StartDate int64 `json:"START_DATE"`
EndDate int64 `json:"END_DATE"`
}
func (t *GroupID) Start() time.Time {
return time.Unix(t.StartDate, 0)
}
func (t *GroupID) End() time.Time {
return time.Unix(t.EndDate, 0)
}
type UCSUserAccess struct {
Access map[string]string `json:"access"`
SubscriptionGroupDates map[string]GroupID `json:"subscription_group_dates"`
}
func main() {
str := `{
"access": {
"STOCK": "1",
"FOREX": "1",
"WEBFOREX": "1",
"WEBSTOCK": "1"
},
"subscription_group_dates": {
"32": {
"START_DATE": 1464753600,
"END_DATE": 1472616000
},
"42": {
"START_DATE": 1470024000,
"END_DATE": 1472616000
}
}
}`
var d UCSUserAccess
err := json.Unmarshal([]byte(str), &d)
if err != nil {
log.Fatal(err)
}
fmt.Println(d)
gID := d.SubscriptionGroupDates["32"]
fmt.Println(gID.Start())
fmt.Println()
body, err := json.Marshal(d)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
4- You may use Int64
with receiver method, like this working sample code:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
type Int64 int64
type GroupID struct {
StartDate Int64 `json:"START_DATE"`
EndDate Int64 `json:"END_DATE"`
}
func (t *Int64) Time() time.Time {
return time.Unix(int64(*t), 0).UTC()
}
type UCSUserAccess struct {
Access map[string]string `json:"access"`
SubscriptionGroupDates map[string]GroupID `json:"subscription_group_dates"`
}
func main() {
str := `{
"access": {
"STOCK": "1",
"FOREX": "1",
"WEBFOREX": "1",
"WEBSTOCK": "1"
},
"subscription_group_dates": {
"32": {
"START_DATE": 1464753600,
"END_DATE": 1472616000
},
"42": {
"START_DATE": 1470024000,
"END_DATE": 1472616000
}
}
}`
var d UCSUserAccess
err := json.Unmarshal([]byte(str), &d)
if err != nil {
log.Fatal(err)
}
fmt.Println(d)
gID := d.SubscriptionGroupDates["32"]
fmt.Println(gID.StartDate.Time())
fmt.Println()
body, err := json.Marshal(d)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
Your structure is a bit off. SubscriptionGroupDates, specifically, is a map of string to a structure, and the structure itself is either a map of strings to ints, or a static structure with two int fields. You have a second struct nested, and your dates are specified as maps when they should just be ints:
type UCSUserAccess struct {
Groups map[string]string `json:"groups"`
Access map[string]string `json:"access"`
IsExpert string `json:"isExpert"`
SubscriptionGroupDates map[string]GroupID `json:"subscription_group_dates"`
}
type GroupID struct {
StartDate int `json:"START_DATE"`
EndDate int `json:"END_DATE"`
}
This gives the JSON as you expect. Example: https://play.golang.org/p/rGm7zKJypk (run the output through jsonlint.com to verify it comes out how you wanted).
As for the timestamp, your timestamps in the JSON are stored as Unix timestamps. In order to get those into a time.Time, you need to parse them yourself, either after unmarshalling, or by implementing the json.Unmarshaller
interface on a custom time type. See this answer for details on how to do that.
You could create your own date simple by supplying UnmarshalJSON and MarshalJSON methods. You can format the handle the input and format the output in any way you please.
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
type Date struct {
year int
month int
day int
}
func (t *Date) UnmarshalJSON(data []byte) (e error) {
trimmed := string(data)
trimmed = strings.TrimLeft(trimmed, "\"")
trimmed = strings.TrimRight(trimmed, "\"")
parts := strings.Split(trimmed, "-")
t.year, _ = strconv.Atoi(parts[0])
t.month, _ = strconv.Atoi(parts[1])
t.day, _ = strconv.Atoi(parts[2])
return
}
func (t *Date) MarshalJSON() (buff []byte, e error) {
buff = []byte(fmt.Sprintf("\"%d-%d-%d\"", t.year, t.month, t.day))
return
}
type Foo struct {
Groups map[string]string `json:"groups"`
Date Date `json:"date"`
}
func main() {
f := Foo{
Groups: map[string]string{
"group1": "bar",
"group2": "baz",
},
Date: Date{year: 2016, month: 12, day: 22},
}
buff, _ := json.Marshal(&f)
fmt.Println(string(buff))
json.Unmarshal(buff, &f)
fmt.Printf("%+v", f)
}