Skip to content

Parsing output of Users.ListFollowers #1176

Closed
@davidobrien1985

Description

@davidobrien1985

Hi,

golang noob here.
Instead of using REST calls I thought I'd use this package here to interact with GitHub.

For example when using Users.ListFollowers() I get all the information back of all the followers.
For a subsequent step I only need the Login property (the GitHub user name).
However, I can't figure out how to work with the output of above call, the type being gitub.User.

How do I parse this output?

Thanks all!

Activity

gmlewis

gmlewis commented on May 26, 2019

@gmlewis
Collaborator

Hi @davidobrien1985, and welcome to Go!
I predict that if you give it a chance, Go will become one of your favorite (dare I say "go-to"?) programming languages!

Pro-tip 1: If you want to get really good at Go really fast, dig into solving problems on a website like https://codingame.com or http://adventofcode.com.

Pro-tip 2: I used to be a snob about IDEs and how they were only for the weak, but VSCode has truly world-class support for Go and I highly recommend using it.

Now, to answer your question, the auto-generated Godocs are a great place to start. Here's the call you are asking about:
https://godoc.org/github.com/google/go-github/github#UsersService.ListFollowers

You'll see that it returns a slice of []*User and if you click on that, you see the User struct:
https://godoc.org/github.com/google/go-github/github#User

Due to the nature of how JSON is handled in Go, we typically use pointers to fields so that we can use omitempty and the fields will be nil if not supplied. As a result, we added "accessors" to make accessing the fields much easier... which you will see below that struct.

So if you only needed the Login property of each user (and optionally wanted to print their name), you could do something like this...

users, _, err := s.ListFollowers(ctx, "gmlewis", nil)
if err != nil { /* handle the error */ }
for _, user := range users {
  log.Printf("%v (%v)", user.GetLogin(), user.GetName())
}

Now actually, this is a simple but not complete example... GitHub has pagination... so if this user has a large number of followers, GitHub breaks up the response into many request/response pairs. I'll respond again later with a more complete example, but hopefully this will get you up-and-running quickly.

Have fun with Go, and feel free to ask questions any time.

gmlewis

gmlewis commented on May 26, 2019

@gmlewis
Collaborator

As promised, here is a complete example to list all the followers of a particular GitHub user:

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"sort"
	"strings"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

const (
	maxPerPage = 1000
)

var (
	followed = flag.String("user", "gmlewis", "GitHub user to query")
)

type githubClient struct {
	client *github.Client
}

func main() {
	flag.Parse()

	tc := oauth2.NewClient(oauth2.NoContext, nil)
	ghc := &githubClient{
		client: github.NewClient(tc),
	}

	followers, err := ghc.followers(*followed)
	if err != nil {
		log.Fatalf("ListFollowers(%v): %v", *followed, err)
	}

	sort.Strings(followers)
	fmt.Printf("%v followers of GitHub user %q: %v", len(followers), *followed, strings.Join(followers, ", "))
}

func (g *githubClient) followers(username string) ([]string, error) {
	ctx := context.Background()

	opt := &github.ListOptions{PerPage: maxPerPage}
	var result []string
	for {
		users, _, err := g.client.Users.ListFollowers(ctx, username, opt)
		if err != nil {
			return nil, err
		}
		if len(users) == 0 {
			break
		}
		for _, user := range users {
			result = append(result, user.GetLogin())
		}
		opt.Page++
	}
	return result, nil
}

Note that GitHub has API query rate limits... which you will quickly become an expert at if you hit their API too hard. I believe the limit is 5000 queries per hour if your program uses an authenticated user, otherwise the rate limit is something like 60 per hour. The program above uses unauthenticated requests.

I hope that helps. Please let me know if you have any questions.

(Also note that this example is not optimal... it makes one extra API call when it could take a look at the response header to see if there are actually more users before making the last API call... but for simplicity I make the extra API call above. Let me know if you really want to see an optimized version.)

gmlewis

gmlewis commented on May 26, 2019

@gmlewis
Collaborator

Here's a better example that shouldn't call the API one extra time.
Note that I've seen some weirdness when playing with the PerPage value. 1000 seems to be the maximum value, but setting it to smaller numbers then increasing the Page number will sometimes get you the same results, so be warned you might need to sanitize the results or experiment if you don't use 1000 for PerPage.

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"sort"
	"strings"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

const (
	maxPerPage = 1000
)

var (
	followed = flag.String("user", "gmlewis", "GitHub user to query")
)

type githubClient struct {
	client *github.Client
}

func main() {
	flag.Parse()

	tc := oauth2.NewClient(oauth2.NoContext, nil)
	ghc := &githubClient{
		client: github.NewClient(tc),
	}

	followers, err := ghc.followers(*followed)
	if err != nil {
		log.Fatalf("ListFollowers(%v): %v", *followed, err)
	}

	sort.Strings(followers)
	fmt.Printf("%v followers of GitHub user %q: %v", len(followers), *followed, strings.Join(followers, ", "))
}

func (g *githubClient) followers(username string) ([]string, error) {
	ctx := context.Background()

	opt := &github.ListOptions{PerPage: maxPerPage}
	var result []string
	for {
		log.Printf("Calling GitHub: %#v", *opt)
		users, resp, err := g.client.Users.ListFollowers(ctx, username, opt)
		if err != nil {
			return nil, err
		}
		log.Printf("Got %v users, resp.NextPage=%v, resp.Header['Link']=%v", len(users), resp.NextPage, resp.Header["Link"])
		for _, user := range users {
			result = append(result, user.GetLogin())
		}
		opt.Page++
		if opt.Page >= resp.NextPage {
			break
		}
	}
	return result, nil
}
gmlewis

gmlewis commented on May 26, 2019

@gmlewis
Collaborator

I'm going to go ahead and close this issue. Feel free to reopen if you have any questions or if I misunderstood your original question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @gmlewis@davidobrien1985

        Issue actions

          Parsing output of Users.ListFollowers · Issue #1176 · google/go-github