Convert Struct to JSON in Golang - go

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

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)
}

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))
}

Related

Optional array in struct

I want to make an array optional in struct and use it with an if else in a func.
type TestValues struct {
Test1 string `json:"test1"`
DefaultTests []string `json:"__tests"`
//DefaultTests *array `json:"__tests,omitempty" validate:"option"`
Test2 string `json:"__Test2"`
}
func (x *Controller) createTest(context *gin.Context, uniqueId string, testBody *TestValues) (*http.Response, error) {
if testBody.DefaultTags {
postBody, err := json.Marshal(map[string]string{
"Test2": testBody.Test2,
"Test1": testBody.Test1,
"defaultTests": testBody.DefaultTests,
"uniqueId": uniqueId,
})
} else {
postBody, err := json.Marshal(map[string]string{
"Test2": testBody.Test2,
"Test1": testBody.Test1,
"uniqueId": uniqueId,
})
}
...
}
When I run the code it tells me that DefaultTests is undefined array but I don't want this error to pop because DefaultTests can existe and sometimes it won't be present in the json that's why I want to make it optional. The if else part is not working too.
It's better to use len() when checking if an array is empty here.
if len(testBody.DefaultTests) > 0 {
...
}
Check the Zero value of the DefaultTests in struct below for more clarity on this behaviour
package main
import "fmt"
type TestValues struct {
Test1 string `json:"test1"`
DefaultTests []string `json:"__tests"`
//DefaultTests *array `json:"__tests,omitempty" validate:"option"`
Test2 string `json:"__Test2"`
}
func main() {
var tv = TestValues{Test1: "test"}
if len(tv.DefaultTests) > 0 {
fmt.Printf("Default Tests: %v\n", tv.DefaultTests)
} else {
fmt.Printf("Default Tests empty value: %v\n", tv.DefaultTests)
}
}
Here's my final code with Marc's answer
func (x *Controller) createTest(context *gin.Context, uniqueId string, testBody *TestValues) (*http.Response, error) {
if len(testBody.DefaultTags) > 0 {
postBody, err := json.Marshal(testBody.DefaultTags)
} else {
postBody, err := json.Marshal(map[string]string{
"Test2": testBody.Test2,
"Test1": testBody.Test1,
"uniqueId": uniqueId,
})
}
...
}

How to extract data from map[string]interface{}?

I am sending the data to the API like following:
{"after": {"amount": 811,"id":123,"status":"Hi"}, "key": [70]}
and i am getting following while printing :
map[after:map[amount:811 id:123 status:Hi ] key:[70]]
Is there any way to print individual field like this??
amount::800
id:123
status:Hi
The code:
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
)
var (
PORT = ":8080"
)
func main() {
fmt.Println("In Main")
http.HandleFunc("/", changedData)
http.ListenAndServe(PORT, nil)
}
type Data struct {
Id int64 `json:"id"`
Amount float64 `json:"amount"`
Status string `json:"status"`
}
type mark map[string]interface{}
func changedData(w http.ResponseWriter, r *http.Request) {
fmt.Println("Coming From API")
reqBody, _ := ioutil.ReadAll(r.Body)
fmt.Println("Data coming from API ", string(reqBody))
digit := json.NewDecoder(strings.NewReader(string(reqBody)))
for digit.More() {
var result mark
err := digit.Decode(&result)
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
break
}
fmt.Println("final_data ", result)
}
}
Decode to a Go type that matches the structure of the JSON document. You declared a type for the "after" field. Wrap that type with a struct to match the document.
func changedData(w http.ResponseWriter, r *http.Request) {
var v struct{ After Data }
err := json.NewDecoder(r.Body).Decode(&v)
if err != nil {
http.Error(w, "bad request", 400)
return
}
fmt.Printf("final_data: %#v", v.After)
}
Playground example.
I think you can define a struct type if you know the JSON file format or if the JSON format is predefined. As far as I know that mostly using interface{} is a way when you don't know the JSON format or there is no predefined format of the JSON. If you define a struct type  and use it while unmarshaling the JSON to struct, you can access the variables by typing like data.Id or data.Status.
Here's an example code:
package main
import (
"encoding/json"
"fmt"
)
type Data struct {
AfterData After `json:"after"`
Key []int `json:"key"`
}
type After struct {
Id int64 `json:"id"`
Amount float64 `json:"amount"`
Status string `json:"status"`
}
func main() {
j := []byte(`{"after": {"amount": 811,"id":123,"status":"Hi"}, "key": [70]}`)
var data *Data
err := json.Unmarshal(j, &data)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(data.AfterData)
fmt.Println(data.AfterData.Id)
fmt.Println(data.AfterData.Amount)
fmt.Println(data.AfterData.Status)
}
Output will be
{123 811 Hi}
123
811
Hi
Go Playground

Iterating list json object in golang

I have this piece of code to read a JSON object. I need to easily iterate over all the elements in the 'outputs'/data/concepts key.
Is there a better way to do it?
Also, how can I access the attributes of value:
value.app_id, value.id..etc
Code:
package main
import (
"encoding/json"
"fmt"
)
var jsonBytes = []byte(`
{"outputs": [{
"data": {"concepts":
[{"app_id": "main",
"id": "ai_GTvMbVGh",
"name": "ancient",
"value": 0.99875855}]
}}
],
"status": {"code": 10000, "description": "Ok"}}`)
func main() {
var output map[string]interface{}
err := json.Unmarshal([]byte(jsonBytes), &output)
if err != nil {
print(err)
}
for _, value := range output["outputs"].([]interface{}) {
//fmt.Println(value.(map[string]interface{})["data"].(map[string]interface{})["concepts"]).([]interface{})
//fmt.Println(value.(map[string]interface{})["data"].(map[string]interface{})["concepts"])
for _, value := range value.(map[string]interface{})["data"].(map[string]interface{})["concepts"].([]interface{}){
fmt.Println(value)
}
}
//fmt.Printf("%+v\n", output)
}
the best way will be to Unmarshal the JSON into an struct and iterate over the values,
func main() {
var output StructName
err := json.Unmarshal([]byte(jsonBytes), &output)
if err != nil {
print(err)
}
for _, value := range output.Outputs {
for _, val := range value.Data.Concepts {
fmt.Printf("AppId:%s\nID:%s\nname:%s\nvalue:%f", val.AppID, val.ID, val.Name, val.Value)
}
}
}
type StructName struct {
Outputs []struct {
Data struct {
Concepts []struct {
AppID string `json:"app_id"`
ID string `json:"id"`
Name string `json:"name"`
Value float64 `json:"value"`
} `json:"concepts"`
} `json:"data"`
} `json:"outputs"`
Status struct {
Code int `json:"code"`
Description string `json:"description"`
} `json:"status"`
}

Unmarshalling array into struct

I'm trying to figure out how I can (using gin) create a struct from an api call
"icon": [
"https://api.figo.me/assets/images/accounts/postbank.png",
{
"48x48": "https://api.figo.me/assets/images/accounts/postbank_48.png",
"60x60": "https://api.figo.me/assets/images/accounts/postbank_60.png",
"72x72": "https://api.figo.me/assets/images/accounts/postbank_72.png",
"84x84": "https://api.figo.me/assets/images/accounts/postbank_84.png",
"96x96": "https://api.figo.me/assets/images/accounts/postbank_96.png",
"120x120": "https://api.figo.me/assets/images/accounts/postbank_120.png",
"144x144": "https://api.figo.me/assets/images/accounts/postbank_144.png",
"192x192": "https://api.figo.me/assets/images/accounts/postbank_192.png",
"256x256": "https://api.figo.me/assets/images/accounts/postbank_256.png"
}
],
into
type CatalogBank struct {
Advice string `json:"advice"`
BankCode string `json:"bank_code"`
BankName string `json:"bank_name"`
BIC string `json:"bic"`
Credentials []struct {
Label string `json:"label"`
Masked bool `json:"masked"`
} `json:"credentials"`
Icon []struct {
} `json:"icon"`
Language []byte `json:"language"`
}
The icon part is just an extract from, but I always get an unmarshall error for this part. How would I have to definde the 'Icon' part in the struct?
This would work
package main
type CatalogBank struct {
Icon []interface{} `json:"icon"`
}
This is a little tricky in Golang because of the non-strict type in the JSON. If that is definitely the format you are going to receive the data in, you should unmarshal to an Interface{} and then parse the interface into a struct that you can use in your Golang
Direct Unmarshalling cannot be done, as the type of each field is not known
type Icon struct{
ImageLink string
ImageLink48 string
// ...
}
type CatalogBank struct {
Advice string `json:"advice"`
IconRaw []interface{} `json:"icon"`
Icon []Icon
//...
}
func UnmarshalIcon(c &CatalogBank, i interface{}):
// first convert it to the top level list
newIcon := Icon{}
listOfIcons := i.([]interface{})
for _, i := range listOfIcons:
switch iT := i.(type) {
case string:
newIcon.ImageLink = iT
case map[string]interface{}:
for smallIconsKey, smallIconLink := range iT {
if smallIconsKey == "48x48"{
newIcon.ImageLink48 = smallIconLink.(string)
}
// and so on
}
var c CatalogBank{}
_ := json.Unmarshal([]byte(your_json), &c)
for _, i := range c.IconRaw:
UnmarshalIcon(&c, i)
Caveat Emptor: I haven't checked above implementation but it should be something like this
You can not use []struct {} for icon, change it to []interface{} instead, or if you want operate on type safe type look at the second solution with cusom unmarshaler
Solution 1
package main
import (
"encoding/json"
"fmt"
"log"
)
type CatalogBank struct {
Advice string `json:"advice"`
BankCode string `json:"bank_code"`
BankName string `json:"bank_name"`
BIC string `json:"bic"`
Credentials []struct {
Label string `json:"label"`
Masked bool `json:"masked"`
} `json:"credentials"`
Icon []interface{} `json:"icon"`
Language []byte `json:"language"`
}
func main() {
data := `
{
"Advice":"abc",
"icon": [
"https://api.figo.me/assets/images/accounts/postbank.png",
{
"48x48": "https://api.figo.me/assets/images/accounts/postbank_48.png",
"60x60": "https://api.figo.me/assets/images/accounts/postbank_60.png",
"72x72": "https://api.figo.me/assets/images/accounts/postbank_72.png",
"84x84": "https://api.figo.me/assets/images/accounts/postbank_84.png",
"96x96": "https://api.figo.me/assets/images/accounts/postbank_96.png",
"120x120": "https://api.figo.me/assets/images/accounts/postbank_120.png",
"144x144": "https://api.figo.me/assets/images/accounts/postbank_144.png",
"192x192": "https://api.figo.me/assets/images/accounts/postbank_192.png",
"256x256": "https://api.figo.me/assets/images/accounts/postbank_256.png"
}
]
}
`
bank := &CatalogBank{}
err := json.Unmarshal([]byte(data), bank)
if err != nil {
log.Fatal(err)
}
for _, icon := range bank.Icon {
fmt.Printf(" %v\n ", icon)
}
}
Solution 2:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Icons struct {
URL string
BySize map[string]string
}
type CatalogBank struct {
Advice string `json:"advice"`
BankCode string `json:"bank_code"`
BankName string `json:"bank_name"`
BIC string `json:"bic"`
Credentials []struct {
Label string `json:"label"`
Masked bool `json:"masked"`
} `json:"credentials"`
Icon *Icons `json:"-,"`
Language []byte `json:"language"`
}
func (p *CatalogBank) Unmarshal(data []byte) error {
type Transient struct {
*CatalogBank
Icon []interface{} `json:"icon"`
}
var transient = &Transient{CatalogBank:p}
err := json.Unmarshal([]byte(data), transient)
if err != nil {
return err
}
p.Icon = &Icons{
BySize: make(map[string]string),
}
if len(transient.Icon) > 0 {
if url, ok := transient.Icon[0].(string); ok {
p.Icon.URL = url
}
if aMap, ok := transient.Icon[1].(map[string]interface{}); ok {
for k, v := range aMap {
p.Icon.BySize[k] = v.(string)
}
}
}
return nil
}
func main() {
data := `
{
"Advice":"abc",
"icon": [
"https://api.figo.me/assets/images/accounts/postbank.png",
{
"48x48": "https://api.figo.me/assets/images/accounts/postbank_48.png",
"60x60": "https://api.figo.me/assets/images/accounts/postbank_60.png",
"72x72": "https://api.figo.me/assets/images/accounts/postbank_72.png",
"84x84": "https://api.figo.me/assets/images/accounts/postbank_84.png",
"96x96": "https://api.figo.me/assets/images/accounts/postbank_96.png",
"120x120": "https://api.figo.me/assets/images/accounts/postbank_120.png",
"144x144": "https://api.figo.me/assets/images/accounts/postbank_144.png",
"192x192": "https://api.figo.me/assets/images/accounts/postbank_192.png",
"256x256": "https://api.figo.me/assets/images/accounts/postbank_256.png"
}
]
}
`
bank := &CatalogBank{}
err := bank.Unmarshal([]byte(data))
if err != nil {
log.Fatal(err)
}
fmt.Printf("advice: %v\n", bank.Advice)
fmt.Printf("icon: %v\n", bank.Icon.URL)
for size, icon := range bank.Icon.BySize {
fmt.Printf("%v => %v\n ",size, icon)
}
}
You can define your icon like this:
package main
import (
"fmt"
"encoding/json"
)
var testIcon = []byte(`{"icon":[
"https://api.figo.me/assets/images/accounts/postbank.png",
{
"48x48":"https://api.figo.me/assets/images/accounts/postbank_48.png"
}]
}`)
func main() {
icon := make(map[string][]interface{})
err := json.Unmarshal(testIcon, &icon)
if err != nil {
panic(err)
}
fmt.Println(icon)
// map[icon:[https://api.figo.me/assets/images/accounts/postbank.png map[48x48:https://api.figo.me/assets/images/accounts/postbank_48.png]]]
}

Embed struct but only have certain fields in json

I have a struct that I want to embed but want to json encode only certain fields of that struct (and lowercase them). Is that possible?
https://play.golang.org/p/bEC4zlx2oC:
package main
import (
"encoding/json"
"fmt"
"net/url"
)
type MyStruct struct {
Name string `json:"name"`
*url.URL
}
func main() {
m := &MyStruct{
"Bob",
&url.URL{
Scheme: "http",
},
}
j, err := json.Marshal(m)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(j)) // want {"name":"Bob","scheme":"http"}
}
Expanding my comment with example.
It is feasible, you have to implement Marshal interface.
For example:
func (u *MyStruct) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Name string `json:"name"`
Scheme string `json:"scheme"`
}{
Name: u.Name,
Scheme: u.Scheme,
})
}
Play Link: https://play.golang.org/p/LLchuOdYvf
Output:
{"name":"Bob","scheme":"http"}

Resources