RESTy
Previously, we went looked at how to send an email through the MailGun API. This time around we'll be updated the code we put together a couple weeks back to pull in a set of users from the jsonplaceholder site. Once updated we'll see if we can make use of it, if you read the last post you might be able to guess what we're going to do...
The core approach is very similar to last time we did this. We have a known format for the JSON object we'll be receiving from the endpoint so we'll start by creating our struct or more correctly - structs. I think we'll break it up into smaller pieces, you can see below while it does have everything we need it's a bit unwieldy I think. To be clear we don't actually need to populate the entire struct with the incoming JSON. We could just get the ID, Name, Username, and Email and that will take care of it. I'm only including everything as an example, the code sample on GitHub likely won't though.
type Users []struct {
ID int `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
Email string `json:"email"`
Address struct {
Street string `json:"street"`
Suite string `json:"suite"`
City string `json:"city"`
Zipcode string `json:"zipcode"`
Geo struct {
Lat string `json:"lat"`
Lng string `json:"lng"`
} `json:"geo"`
} `json:"address"`
Phone string `json:"phone"`
Website string `json:"website"`
Company struct {
Name string `json:"name"`
CatchPhrase string `json:"catchPhrase"`
Bs string `json:"bs"`
} `json:"company"`
}
Working from the top down, we'll first pull out Address
type Address struct {
Street string `json:"street"`
Suite string `json:"suite"`
City string `json:"city"`
Zipcode string `json:"zipcode"`
Geo struct {
Lat string `json:"lat"`
Lng string `json:"lng"`
} `json:"geo"`
}
I'm half tempted to break out Geo
, but in the end I think it can stay as part of the address struct. Now we'll move out Company
.
type Company struct {
Name string `json:"name"`
CatchPhrase string `json:"catchPhrase"`
Bs string `json:"bs"`
}
And finally, the Users
block using the newly created Address
and Company
structs.
type Users []struct {
ID int `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
Email string `json:"email"`
Address Address `json:"address"`
Phone string `json:"phone"`
Website string `json:"website"`
Company Company `json:"company"`
}
The nice thing about iterating on that same base code we started with is we only have to make slight changes. We'll alter our variable that had been r
, to be called u
which will hold our Users
.
var u Users
json.Unmarshal(body, &u)
OK, so we've got our users in memory now what can we do with them. Let's imagine they were on our mailing list and wanted an update each time a new post went up. We know from last time that sending an email through MailGun is very easy - in fact, we have a package we can just import. It's almost like I planned it that way! Before we get to that though...
Templating
Let's take a quick detour into Go templates. If you've done any web development you may be familiar with Handlebars or similar template systems there - this is more or less the same thing. We can take struct and use it to replace text within a "marked up" piece of text. Here is a basic example which you can see running on the Golang Playground. Let's go over what we're doing.
package main
import (
"os"
"text/template"
)
type Data struct {
Name string
City string
}
func main() {
o := Data{"Steve", "Portland"}
msgText := "It's {{.Name}} from {{.City}}!"
t := template.Must(template.New("msg").Parse(msgText))
err := t.Execute(os.Stdout, o)
if err != nil {
panic(err)
}
}
In this example, you can see we're declaring our template text, It's {{.Name}} from {{.City}}!
. Then we wrap template.New().Parse()
with template.Must()
. .Must()
will panic if .New()
or .Parse()
fails. t.Execute()
does the actual work - in this case replacing the {{}}
tokens and writing the result directly to standard out. You can see this in action over on the Golang Playground. Spoilers, it prints out It's Steve from Portland!
.
We can use our Users
and templates to very quickly do substitutions and end up with a nice personalized email.
Work That List
Back to our original code, we'll be adding the first part of the templating code just under our now updated Users
variable and Unmarshal()
call.
msgText := "To: {{.Email}}\nHi {{.Username}}! There is a new post!\n\n\n"
t := template.Must(template.New("msg").Parse(msgText))
As you can see we are using a very similar setup to our templating example. Here we are replacing email and username with whatever is contained in the object passed in. But how do we use this with our Users
? In this case, we're going to just use for
/range
to loop through.
for _, v := range u {
err := t.Execute(os.Stdout, v)
if err != nil {
panic(err)
}
}
v
will hold the value of one of our Users
during each time through the for loop. We don't need the index in this case so we're using _
to tell Go we don't care about that variable. As with the previous example, we call t.Execute()
and let it write directly to standard out. Finally, let's take a look at our updated code.
Full code listing
package main
import (
"encoding/json"
"io/ioutil"
"net/http"
"os"
"text/template"
)
type Address struct {
Street string `json:"street"`
Suite string `json:"suite"`
City string `json:"city"`
Zipcode string `json:"zipcode"`
Geo struct {
Lat string `json:"lat"`
Lng string `json:"lng"`
} `json:"geo"`
}
type Company struct {
Name string `json:"name"`
CatchPhrase string `json:"catchPhrase"`
Bs string `json:"bs"`
}
type Users []struct {
ID int `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
Email string `json:"email"`
Address Address `json:"address"`
Phone string `json:"phone"`
Website string `json:"website"`
Company Company `json:"company"`
}
func main() {
APIURL := "https://jsonplaceholder.typicode.com/users"
req, err := http.NewRequest(http.MethodGet, APIURL, nil)
if err != nil {
panic(err)
}
client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
var u Users
json.Unmarshal(body, &u)
msgText := "To: {{.Email}}\nHi {{.Username}}! There is a new post!\n\n\n"
t := template.Must(template.New("msg").Parse(msgText))
for _, v := range u {
err := t.Execute(os.Stdout, v)
if err != nil {
panic(err)
}
}
}
Not too exciting by we did show how we can nest structs and introduced some basic templating. This post is getting a tad long so... Next time, we'll refactor it a little and add in some actual mail sending. After that, I'm not sure yet!
You can find the code for this and most of the other Attempting to Learn Go posts in the repo on GitHub.
shindakun / atlg
Source repo for the "Attempting to Learn Go" posts I've been putting up over on dev.to
Attempting to Learn Go
Here you can find the code I've been writing for my Attempting to Learn Go posts that I've been writing and posting over on Dev.to.
Post Index
Enjoy this post? |
---|
How about buying me a coffee? |
Top comments (0)