7.9 KiB
Introduction to Go: JSON
In programming languages, you will very often deal with data structures internally.
Sometimes, you need to pass data outside of your application or read data from another application, or even a file.
API, for example often expose data in json
, xml
, grpc
and all sorts of formats.
Before we can really take a look at building APIs and even storing data in databases,
we need some fundamental knowledge about how to convert these data structures, into structures
that our application can understand.
In part 1, we dealt with Variables and more importantly,
we dealt with struct
data type, which made us build a customer
object.
If we wanted to pass the customer
to a database, or an external system, it's often required that we convert this to json
format.
Dev Environment
The same as Part 1, we start with a dockerfile where we declare our version of go
.
cd golang\introduction\part-2.json
docker build --target dev . -t go
docker run -it -v ${PWD}:/work go sh
go version
Create our App
Create a new directory that holds defines our repository
and holds our module
mkdir videos
- Define a module path
# change directory to your application source code
cd videos
# create a go module file
go mod init videos
Create our base code
We start out all our applications with a main.go
defining our package
, declaring our import
dependencies
and our entrypoint main()
function
package main
import "fmt"
func main() {
fmt.Println("Hello, world.")
}
Create our Videos app
Firstly, we create a seperate code file videos.go
that deals with our YouTube videos
Similar to Part 1, we define the file, we define what a video looks like using a struct
and a function for returning a slice of videos.
package main
import ()
type video struct {
Id string
Title string
Description string
Imageurl string
Url string
}
func getVideos()(videos []video){
//Get our videos here,
//and return them
return videos
}
Populating our Video Struct
Similar to our customers app, we created a struct
and populated it with data:
Let's add the following to our getVideos()
function:
video1 := video{
Id : "eyvLwK5C2dw",
Title : "Kubernetes on Azure",
Imageurl : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
Url : "https://youtu.be/eyvLwK5C2dw",
Description : "",
}
video2 := video{
Id : "QThadS3Soig",
Title : "Kubernetes on Amazon",
Imageurl : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
Url : "https://youtu.be/QThadS3Soig",
Description : "",
}
return []video{ video1, video2}
We need to also invoke our function that will return the videos
Don't worry about where the videos will come from.
We always start with the building blocks and move on from there
In our main()
function:
func main() {
videos := getVideos()
fmt.Println(videos)
}
Files
Now, Ideally, we do not want to "hard code" our videos like this.
Currently, our videos are embedded into the code and we can only return 2 videos.
If we want to introduce a new video, we have to rebuild the application.
In the real world, videos are our data.
Data lives outside of the application. Like in a database.
Technically, we can use a database, but that is too big of a step for now.
So let's start with a file instead.
Introducing ioutil
It's important to learn how to navigate and read go documentation. Let's go to https://golang.org/pkg/io/ioutil/
We can import this library since its part of the Go standard library set.
import (
"io/ioutil"
)
Let's move our videos into a json
file called videos.json
:
[
{
"id" : "QThadS3Soig",
"title" : "Kubernetes on Amazon",
"imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
"url" : "https://youtu.be/QThadS3Soig",
"description" : ""
},
{
"id" : "eyvLwK5C2dw",
"title" : "Kubernetes on Azure",
"imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
"url" : "https://youtu.be/eyvLwK5C2dw",
"description" : ""
}
]
Reading a file
Let's take another look at https://golang.org/pkg/io/ioutil/
Notice the ReadFile
function
We can read a file from disk:
fileBytes, err := ioutil.ReadFile("./videos.json")
if err != nil {
panic(err)
}
fileContent := string(fileBytes)
fmt.Println(fileContent)
Panic
Also notice the function panic
.
"A panic typically means something went unexpectedly wrong. Mostly we use it to fail fast on errors that shouldn’t occur during normal operation, or that we aren’t prepared to handle gracefully."
For now, we will panic on every potential error.
In a future video, we'll cover Error handling in more depth
JSON
Working with JSON is pretty straightforward in go.
struct
objects are pretty well synergized with json
Let's take a look at the json
package that is part of go.
https://golang.org/pkg/encoding/json/
"encoding/json"
This package allows us to marshal and unmarshal JSON.
This allows conversion between a struct
or a go type, and json
Unmarshal
From JSON(bytes) ==> Struct\Go type
err = json.Unmarshal(fileBytes, &videos)
if err != nil {
panic(err)
}
return videos
Using our Data
Now in the real world, you would use your data to do something.
Let's say we'd like to update some common terms and conditions to the video description
In our main
function, let's loop the videos & update the description.
Feel free to checkout Part 1 of the series on loops
!
for _, video := range videos {
}
Now we dont want to override the description, we want to inject the terms and conditions on a new line
video.Description = video.Description + "\n Remember to Like & Subscribe!"
Note that when we run this, it does not print our new description field
This is because the loop range
is giving us a copy of the video object.
This means we are updating a copy, but printing out the original.
We need to use the loop indexer and update the original object in the slice.
for i, _ := range videos {
videos[i].Description = videos[i].Description + "\n Remember to Like & Subscribe!"
}
Run our app again and now it updates the original video!
Marshal
https://golang.org/pkg/encoding/json/#Marshal From Struct\Go type ==> JSON(bytes)
Let's create a new function for saving our video back to file
func saveVideos(videos []video)(){
videoBytes, err := json.Marshal(videos)
if err != nil {
panic(err)
}
}
Writing to File
https://golang.org/pkg/io/ioutil/
err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644)
if err != nil {
panic(err)
}
Then in our main
function we can save our videos after the update:
saveVideos(videos)
Mapping fields
In our example our json
and our struct fields match exactly.
Sometimes this is not possible to maintain.
Sometimes we need to tell go, to map certain json
fields to certain fields in our struct
We can do it like so:
type video struct {
Id string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Imageurl string `json:"imageurl"`
Url string `json:"url"`
}