在Golang中将Struct转换为JSON

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)

}