loading static data into nested struct in Go - json

I am looking to implement something very similar to this GoPlayground example:
https://go.dev/play/p/B4JOVwgdwUk
However my data structure is a bit more complex, with the nested struct called Tag. I can't quite get the syntax correct for how to load the data into the struct in the toFile variable declaration.
Can someone help me figure out what I am doing wrong? I have tried what is displayed in the full code below, as well as this:
Tags: []Tag{
[{"appID:": "go", "category": "backend"},{"appID": "fiber", "category": "framework"}]
}
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
func main() {
type Stack struct {
Reputation int `json:"reputation"`
UserID int `json:"user_id"`
UserType string `json:"user_type"`
ProfileImage string `json:"profile_image"`
DisplayName string `json:"display_name"`
Link string `json:"link"`
Tags []Tag `json:"tags"`
}
type Tag struct {
AppID string `json: "appID:,omitempty"`
Category string `json: "category:,omitempty"`
}
toFile := Stack{
Reputation: 141,
UserID: 9820773,
UserType: "registered",
ProfileImage: "https://graph.facebook.com/10209865263541884/picture?type=large",
DisplayName: "Joe Smith",
Link: "https://stackoverflow.com/users/9820773/joe-smith",
Tags: Tag{
[{"appID:": "go", "category": "backend"},{"appID": "fiber", "category": "framework"}]
} // cannot get this to work
}
// Write it out!
tmpFile, err := ioutil.TempFile(os.TempDir(), "sample-")
if err != nil {
panic(err)
}
defer os.Remove(tmpFile.Name())
err = json.NewEncoder(tmpFile).Encode(toFile)
if err != nil {
panic(err)
}
err = tmpFile.Close()
if err != nil {
panic(err)
}
// Let's read it in!
tmpFile2, err := os.Open(tmpFile.Name())
if err != nil {
panic(err)
}
var fromFile Person
err = json.NewDecoder(tmpFile2).Decode(&fromFile)
if err != nil {
panic(err)
}
err = tmpFile2.Close()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", fromFile)
}

The comment from #Kousik helped me fix up the code and run it successfully on the GoPlayground:
https://go.dev/play/p/WmZ9m_55W3S
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
func main() {
type Tag struct {
AppID string `json: "appID:,omitempty"`
Category string `json: "category:,omitempty"`
}
type Stack struct {
Reputation int `json:"reputation"`
UserID int `json:"user_id"`
UserType string `json:"user_type"`
ProfileImage string `json:"profile_image"`
DisplayName string `json:"display_name"`
Link string `json:"link"`
Tags []Tag `json:"tags"`
}
toFile := Stack{
Reputation: 141,
UserID: 9820773,
UserType: "registered",
ProfileImage: "https://graph.facebook.com/10209865263541884/picture?type=large",
DisplayName: "Joe Smith",
Link: "https://stackoverflow.com/users/9820773/joe-smith",
Tags: []Tag{{AppID: "id1", Category: "cat1"}, {AppID: "id2", Category: "cat2"}},
}
// Write it out!
tmpFile, err := ioutil.TempFile(os.TempDir(), "sample-")
if err != nil {
panic(err)
}
defer os.Remove(tmpFile.Name())
err = json.NewEncoder(tmpFile).Encode(toFile)
if err != nil {
panic(err)
}
err = tmpFile.Close()
if err != nil {
panic(err)
}
// Let's read it in!
tmpFile2, err := os.Open(tmpFile.Name())
if err != nil {
panic(err)
}
var fromFile Stack
err = json.NewDecoder(tmpFile2).Decode(&fromFile)
if err != nil {
panic(err)
}
err = tmpFile2.Close()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", fromFile)
}

Related

Parse json data in golang

I am trying to print out a json value that I am getting from a public API.
my json response:
[
{
"address": "0x6679eb24f59dfe111864aec72b443d1da666b360",
"name": "ARIVA",
"symbol": "ARV",
"logo": null,
"logo_hash": null,
"thumbnail": null,
"decimals": "8",
"block_number": "8242108",
"validated": 1
}
]
the code:
type TokenNamingInfo struct {
TokenName string `json:"name"`
TokenAddress string `json:"address"`
TokenSymbol string `json:"symbol"`
}
reqmeta, err := http.NewRequest("GET", "https://somesite.com", nil)
if err != nil {
// handle err
}
reqmeta.Header.Set("Accept", "application/json")
reqmeta.Header.Set("X-Api-Key", "api")
respmeta, err := http.DefaultClient.Do(reqmeta)
if err != nil {
// handle err
}
defer respmeta.Body.Close()
var responseTokenSymbol TokenNamingInfo
json.Unmarshal(tokensymbol, &responseTokenPrice)
However, this is not working and does not print.
fmt.Println(responseTokenSymbol.name)
this is working if my json and struct looks like the below, with the same above procedure.
{
"nativePrice": {
"value": "1906303859440",
"decimals": 18,
"name": "Binance Coin",
"symbol": "BNB"
},
"usdPrice": 0.000875604424294528,
"exchangeAddress": "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73",
"exchangeName": "PancakeSwap v2"
}
type TokenPriceInfo struct {
Price float64 `json:"usdPrice"`
ExchangeName string `json:"exchangeName"`
ExchangeAddress string `json:"exchangeAddress"`
}
You want something like that:
import (
"io/ioutil"
"net/http"
"fmt"
"encoding/json"
)
type TokenNamingInfo struct {
TokenName string `json:"name"`
TokenAddress string `json:"address"`
TokenSymbol string `json:"symbol"`
Logo string `json:"logo"`
LogoHash string `json:"logo_hash"`
Thumbnail string `json:"thumbnail"`
Decimals int `json:"decimals"`
BlockNumber string `json:"block_number"`
Validated string `json:"validated"`
}
reqmeta, err := http.NewRequest("GET", "https://somesite.com", nil)
if err != nil {
// handle err
}
reqmeta.Header.Set("Accept", "application/json")
reqmeta.Header.Set("X-Api-Key", "api")
respmeta, err := http.DefaultClient.Do(reqmeta)
if err != nil {
// handle err
}
defer respmeta.Body.Close()
body, err := ioutil.ReadAll(respmeta.Body)
if err != nil {
// handle err
}
fmt.Println(string(body)) // Print result
responseTokenSymbol := &TokenNamingInfo{}
err = json.Unmarshal(body, responseTokenSymbol)
if err != nil {
// handle error
}
fmt.Println(responseTokenSymbol.TokenName) // Print name

No Values when Loading Json from File Golang

I hope someone could help me with this issue because I have been scratching my head for a while.
I have a project where I am trying to load json into a struct in go. I have followed exactly several tutorials online, but keep getting no data back and no error.
My json file is called page_data.json and looks like:
[
{
"page_title": "Page1",
"page_description": "Introduction",
"link": "example_link",
"authors":
[
"Author1",
"Author2",
"Author3",
]
},
// second object, same as the first
]
But when I try the following in go:
package main
import (
"fmt"
"encoding/json"
"os"
"io/ioutil"
)
type PageData struct {
Title string `json: "page_title"`
Description string `json: "page_description"`
Link string `json: "link"`
Authors []string `json: "authors"`
}
func main() {
var numPages int = LoadPageData("page_data.json")
fmt.Printf("Num Pages: %d", numPages)
}
func LoadPageData(path string) int {
jsonFile, err := os.Open(path)
if err != nil {
fmt.Println(err)
}
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
var pageList []PageData
json.Unmarshal(byteValue, &pageList)
return len(pageList)
}
the output I get is:
Num Pages: 0
Fix the JSON commas and the Go struct field tags. For example,
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
type PageData struct {
Title string `json:"page_title"`
Description string `json:"page_description"`
Link string `json:"link"`
Authors []string `json:"authors"`
}
func main() {
var numPages int = LoadPageData("page_data.json")
fmt.Printf("Num Pages: %d\n", numPages)
}
func LoadPageData(path string) int {
jsonFile, err := os.Open(path)
if err != nil {
fmt.Println(err)
}
defer jsonFile.Close()
byteValue, err := ioutil.ReadAll(jsonFile)
if err != nil {
fmt.Println(err)
}
var pageList []PageData
err = json.Unmarshal(byteValue, &pageList)
if err != nil {
fmt.Println(err)
}
fmt.Println(pageList)
return len(pageList)
}
Output:
[{Page1 Introduction example_link [Author1 Author2 Author3]}]
page_data.json:
[
{
"page_title": "Page1",
"page_description": "Introduction",
"link": "example_link",
"authors":
[
"Author1",
"Author2",
"Author3"
]
}
]

How to Unmarshal an inconsistent JSON field that can be a string *or* an array of string?

I am having trouble Unmarshalling some Json I don't have control over.
There is one field that 99% of the time is a string but occasionally is an array.
type MyListItem struct {
Date string `json:"date"`
DisplayName string `json:"display_name"`
}
type MyListings struct {
CLItems []MyListItem `json:"myitems"`
}
var mylist MyListings
err = json.Unmarshal(jsn, &mylist)
if err != nil {
fmt.Print("JSON:\n%s\n error:%v\n", string(jsn),err)
return
}
Json is as follows:
{
"date": "30 Apr",
"display_name": "Mr Smith"
},
{
"date": "30 Apr",
"display_name": ["Mr Smith", "Mr Jones"],
}
error: json: cannot unmarshal array into Go struct field MyListItem.display_name of type string
Use json.RawMessage to capture the varying field.
Use the json "-" name to hide the DisplayName field from decoder. The application will fill this field after the top-level JSON is decoded.
type MyListItem struct {
Date string `json:"date"`
RawDisplayName json.RawMessage `json:"display_name"`
DisplayName []string `json:"-"`
}
Unmarshal the top-level JSON:
var li MyListItem
if err := json.Unmarshal(data, &li); err != nil {
// handle error
}
Unmarshal the display name depending on the type of the raw data:
if len(li.RawDisplayName) > 0 {
switch li.RawDisplayName[0] {
case '"':
if err := json.Unmarshal(li.RawDisplayName, &li.DisplayName); err != nil {
// handle error
}
case '[':
var s []string
if err := json.Unmarshal(li.RawDisplayName, &s); err != nil {
// handle error
}
// Join arrays with "&" per OP's comment on the question.
li.DisplayName = strings.Join(s, "&")
}
}
playground example
Incorporate the above into a for loop to handle MyListings:
var listings MyListings
if err := json.Unmarshal([]byte(data), &listings); err != nil {
// handle error
}
for i := range listings.CLItems {
li := &listings.CLItems[i]
if len(li.RawDisplayName) > 0 {
switch li.RawDisplayName[0] {
case '"':
if err := json.Unmarshal(li.RawDisplayName, &li.DisplayName); err != nil {
// handle error
}
case '[':
var s []string
if err := json.Unmarshal(li.RawDisplayName, &s); err != nil {
// handle error
}
li.DisplayName = strings.Join(s, "&")
}
}
}
playground example
If there's more than one place in the data model where a value can be a string or []string, it can be helpful to encapsulate the logic in a type. Parse the JSON data in an implementation of the json.Unmarshaler interface.
type multiString string
func (ms *multiString) UnmarshalJSON(data []byte) error {
if len(data) > 0 {
switch data[0] {
case '"':
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
*ms = multiString(s)
case '[':
var s []string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
*ms = multiString(strings.Join(s, "&"))
}
}
return nil
}
Use it like this:
type MyListItem struct {
Date string `json:"date"`
DisplayName multiString `json:"display_name"`
}
type MyListings struct {
CLItems []MyListItem `json:"myitems"`
}
var listings MyListings
if err := json.Unmarshal([]byte(data), &listings); err != nil {
log.Fatal(err)
}
Playground Example
Here's the code to get the value as a slice of strings instead of as a single string with values joined by &.
type multiString []string
func (ms *multiString) UnmarshalJSON(data []byte) error {
if len(data) > 0 {
switch data[0] {
case '"':
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
*ms = multiString{s}
case '[':
if err := json.Unmarshal(data, (*[]string)(ms)); err != nil {
return err
}
}
}
return nil
}
Playground example.
As an alternative, this builds off of the answer from #ThunderCat but instead of using json.RawMessage, uses interface{} and a type switch:
package main
import (
"encoding/json"
"fmt"
"log"
)
type MyListItem struct {
Date string `json:"date"`
DisplayName string `json:"-"`
RawDisplayName interface{} `json:"display_name"`
}
func (li *MyListItem) UnmarshalJSON(data []byte) error {
type localItem MyListItem
var loc localItem
if err := json.Unmarshal(data, &loc); err != nil {
return err
}
*li = MyListItem(loc)
switch li.RawDisplayName.(type) {
case string:
li.DisplayName = li.RawDisplayName.(string)
case []interface{}:
vals := li.RawDisplayName.([]interface{})
if len(vals) > 0 {
li.DisplayName, _ = vals[0].(string)
for _, v := range vals[1:] {
li.DisplayName += "&" + v.(string)
}
}
}
return nil
}
func test(data string) {
var li MyListItem
if err := json.Unmarshal([]byte(data), &li); err != nil {
log.Fatal(err)
}
fmt.Println(li.DisplayName)
}
func main() {
test(`
{
"date": "30 Apr",
"display_name": "Mr Smith"
}`)
test(`
{
"date": "30 Apr",
"display_name": ["Mr Smith", "Mr Jones"]
}`)
}
playground

got an error when i tried to call the value with go lang

I just starting learn this Go Lang programming, and now i'm stuck with the [] things, I tried to create a blog using the Go Lang and i'm using a template, there's no problem with the template, it just I want to append a data that I got from json file.
If I just take the data and send it through the file it's already done, but the problem is when I tried to append the slug to the data (because the json file i got no slug url in it.
That's why I want to get the title of the post then make a slug with it, then
package main
import (
"encoding/json"
"fmt"
"github.com/gosimple/slug"
"html/template"
"io/ioutil"
"net/http"
"os"
)
type Blog struct {
Title string
Author string
Header string
}
type Posts struct {
Posts []Post `json:"posts"`
}
type Post struct {
Title string `json:"title"`
Author string `json:"author"`
Content string `json:"content"`
PublishDate string `json:"publishdate"`
Image string `json:"image"`
}
type BlogViewModel struct {
Blog Blog
Posts []Post
}
func loadFile(fileName string) (string, error) {
bytes, err := ioutil.ReadFile(fileName)
if err != nil {
return "", err
}
return string(bytes), nil
}
func loadPosts() []Post {
jsonFile, err := os.Open("source/posts.json")
if err != nil {
fmt.Println(err)
}
fmt.Println("Successfully open the json file")
defer jsonFile.Close()
bytes, _ := ioutil.ReadAll(jsonFile)
var post []Post
json.Unmarshal(bytes, &post)
return post
}
func handler(w http.ResponseWriter, r *http.Request) {
blog := Blog{Title: "asd", Author: "qwe", Header: "zxc"}
posts := loadPosts()
viewModel := BlogViewModel{Blog: blog, Posts: posts}
t, _ := template.ParseFiles("template/blog.html")
t.Execute(w, viewModel)
}
The error is show in the main function
func main() {
posts := loadPosts()
for i := 0; i < len(posts.Post); i++ { // it gives me this error posts.Post undefined (type []Post has no field or method Post)
fmt.Println("Title: " + posts.Post[i].Title)
}
// http.HandleFunc("/", handler)
// fs := http.FileServer(http.Dir("template"))
// http.Handle("/assets/css/", fs)
// http.Handle("/assets/js/", fs)
// http.Handle("/assets/fonts/", fs)
// http.Handle("/assets/images/", fs)
// http.Handle("/assets/media/", fs)
// fmt.Println(http.ListenAndServe(":9000", nil))
}
I already try to solved it a couple of hours but I hit the wall, I think it's possible but I just don't find the way, I don't know what is the good keyword to solve the problem.
And I don't if I already explain good enough or not. Please help me, thank you
This is the JSON file format
{
"posts": [
{
"title": "sapien ut nunc",
"author": "Jeni",
"content": "jgwilt0#mapquest.com",
"publishdate": "26.04.2017",
"image": "http://dummyimage.com/188x199.png/cc0000/ffffff"
},
{
"title": "mus vivamus vestibulum sagittis",
"author": "Analise",
"content": "adonnellan1#biblegateway.com",
"publishdate": "13.03.2017",
"image": "http://dummyimage.com/182x113.bmp/ff4444/ffffff"
}
]
}
You're loadPost method returns []Post. Your definition of Post does not contain the attribute Post. Your Posts struct does.
Here is a modified working example. I didn't define your other structures for brevity.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
)
var rawJson = `{
"posts": [
{
"title": "sapien ut nunc",
"author": "Jeni",
"content": "jgwilt0#mapquest.com",
"publishdate": "26.04.2017",
"image": "http://dummyimage.com/188x199.png/cc0000/ffffff"
},
{
"title": "mus vivamus vestibulum sagittis",
"author": "Analise",
"content": "adonnellan1#biblegateway.com",
"publishdate": "13.03.2017",
"image": "http://dummyimage.com/182x113.bmp/ff4444/ffffff"
}
]
}`
type Data struct {
Posts []struct {
Title string `json:"title"`
Author string `json:"author"`
Content string `json:"content"`
Publishdate string `json:"publishdate"`
Image string `json:"image"`
} `json:"posts"`
}
func loadFile(fileName string) (string, error) {
bytes, err := ioutil.ReadFile(fileName)
if err != nil {
return "", err
}
return string(bytes), nil
}
func loadData() (Data, error) {
var d Data
// this is commented out so that i can load raw bytes as an example
/*
f, err := os.Open("source/posts.json")
if err != nil {
return d, err
}
defer f.Close()
bytes, _ := ioutil.ReadAll(f)
*/
// replace rawJson with bytes in prod
json.Unmarshal([]byte(rawJson), &d)
return d, nil
}
func main() {
data, err := loadData()
if err != nil {
log.Fatal(err)
}
for i := 0; i < len(data.Posts); i++ {
fmt.Println("Title: " + data.Posts[i].Title)
}
/*
// you can also range over the data.Posts if you are not modifying the data then using the index is not necessary.
for _, post := range data.Posts {
fmt.Println("Title: " + post.Title)
}
*/
}
modified just for files
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
)
type Data struct {
Posts []struct {
Title string `json:"title"`
Author string `json:"author"`
Content string `json:"content"`
Publishdate string `json:"publishdate"`
Image string `json:"image"`
} `json:"posts"`
}
func loadData() (Data, error) {
var d Data
b, err := ioutil.ReadFile("source/posts.json")
if err != nil {
return d, err
}
err = json.Unmarshal(b, &d)
if err != nil {
return d, err
}
return d, nil
}
func main() {
data, err := loadData()
if err != nil {
log.Fatal(err)
}
for _, post := range data.Posts {
fmt.Println("Title: " + post.Title)
}
}

Golang - Cannot access map in []interface{}

I have used json.Unmarshal and extracted json content. I then managed to get one layer deeper into the []interface{} by using the following code:
response, err := http.Get("http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=2B2A0C37AC20B5DC2234E579A2ABB11C&steamids=76561198132612090")
content, err := ioutil.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
panic(0)
}
var decoded map[string]interface{}
if err := json.Unmarshal(content, &decoded); err != nil {
panic(0)
}
players := decoded["response"].(map[string]interface{})["players"]
if err != nil {
panic(0)
}
Variable players' type is []interface {} and content is [map[personaname:Acidic]].
How do I access this map? I've tried players["personaname"] but that doesn't seem to work. Any ideas?
Defining a struct type with the expected schema will make your life easier when you want to get the data from it:
package main
import "fmt"
//import "net/http"
//import "io/ioutil"
import "encoding/json"
// you don't need to define everything, only what you need
type Player struct {
Steamid string
Communityvisibilitystate int
Personaname string
Lastlogoff int64 // time.Unix(Lastlogoff, 0)
Profileurl string
Avatar string
Avatarmedium string
Avatarfull string
Personastate int
Realname string
Primaryclanid string
Timecreated int64 // time.Unix(Timecreated, 0)
Personastateflags int
//Loccountrycode string // e.g. if you don't need this
}
func main() {
/*response, err := http.Get("http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=2B2A0C37AC20B5DC2234E579A2ABB11C&steamids=76561198132612090")
if err != nil {
panic(err)
}
content, err := ioutil.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
panic(0)
}*/
content := []byte(`{
"response": {
"players": [
{
"steamid": "76561198132612090",
"communityvisibilitystate": 3,
"profilestate": 1,
"personaname": "Acidic",
"lastlogoff": 1459489924,
"profileurl": "http://steamcommunity.com/id/ari9/",
"avatar": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/bc/bc50a4065c31c606e51dfad329341b2d1f1ac4d3.jpg",
"avatarmedium": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/bc/bc50a4065c31c606e51dfad329341b2d1f1ac4d3_medium.jpg",
"avatarfull": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/bc/bc50a4065c31c606e51dfad329341b2d1f1ac4d3_full.jpg",
"personastate": 3,
"realname": "Ari Seyhun",
"primaryclanid": "103582791440552060",
"timecreated": 1397199406,
"personastateflags": 0,
"loccountrycode": "TR"
}
]
}
}`)
var decoded struct {
Response struct {
Players []Player
}
}
if err := json.Unmarshal(content, &decoded); err != nil {
panic(err)
}
fmt.Printf("%#v\n", decoded.Response.Players)
}
http://play.golang.org/p/gVPRwLFunF
You can also create a new named type from time.Time for Timecreated and Lastlogoff with its own UnmarshalJSON function, and immediately convert it to time.Time using time.Unix()
Players is a JSON array. Thus you have to convert it to a slice of interface.
Then you can access any element of the slice and casting it to a map[string]interface{} type.
Here's the working example
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
response, err := http.Get("http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=2B2A0C37AC20B5DC2234E579A2ABB11C&steamids=76561198132612090")
content, err := ioutil.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
panic(0)
}
var decoded map[string]interface{}
if err := json.Unmarshal(content, &decoded); err != nil {
panic(0)
}
players := decoded["response"].(map[string]interface{})["players"]
if err != nil {
panic(0)
}
sliceOfPlayers := players.([]interface{})
fmt.Println((sliceOfPlayers[0].(map[string]interface{}))["personaname"])
}