2023-02-07 12:19:01 +11:00

7.9 KiB
Raw Blame History

Introduction to Go: JSON

introduction to Go part 2

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 shouldnt occur during normal operation, or that we arent 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"`
}