Skip to content

Instantly share code, notes, and snippets.

@Gurpartap
Created September 9, 2018 06:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gurpartap/ad8e2f8c3831f4cbbb41f6916ff97084 to your computer and use it in GitHub Desktop.
Save Gurpartap/ad8e2f8c3831f4cbbb41f6916ff97084 to your computer and use it in GitHub Desktop.
#golang jsonparser with twitter api responses
package twitter_api
import (
"fmt"
"strconv"
"github.com/buger/jsonparser"
)
/*
The pseudocode to iterate over all responses from a cursored endpoint is:
cursor = -1
api_path = "https://api.twitter.com/1.1/endpoint.json?screen_name=targetUser"
do {
url_with_cursor = api_path + "&cursor=" + cursor
response_dictionary = perform_http_get_request_for_url( url_with_cursor )
cursor = response_dictionary[ 'next_cursor' ]
}
while ( cursor != 0 )
From https://developer.twitter.com/en/docs/basics/cursoring
*/
type CursoredResponse struct {
NextCursor int64
PreviousCursor int64
IDs []int64
}
func (cursored *CursoredResponse) From(data *[]byte) {
if data == nil || *data == nil {
return
}
jsonparser.EachKey(*data, func(idx int, value []byte, dataType jsonparser.ValueType, err error) {
if err != nil {
panic(err)
}
switch idx {
case 0:
if dataType != jsonparser.Number {
panic(fmt.Sprintf("unexpected data type %s", dataType))
}
cursored.NextCursor, err = jsonparser.ParseInt(value)
if err != nil {
panic(err)
}
case 1:
if dataType != jsonparser.Number {
panic(fmt.Sprintf("unexpected data type %s", dataType))
}
cursored.PreviousCursor, err = jsonparser.ParseInt(value)
if err != nil {
panic(err)
}
case 2:
_, err = jsonparser.ArrayEach(value, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
if err != nil {
panic(err)
}
if dataType != jsonparser.Number {
panic(fmt.Sprintf("unexpected data type %s", dataType))
}
i, err := strconv.ParseInt(string(value), 10, 64)
if err != nil {
panic(err)
}
cursored.IDs = append(cursored.IDs, i)
})
if err != nil {
panic(err)
}
}
}, [][]string{
{"next_cursor"},
{"previous_cursor"},
{"ids"},
}...)
}
package twitter_api
import (
"reflect"
"testing"
)
func TestCursored_From(t *testing.T) {
type fields struct {
NextCursor int64
PreviousCursor int64
IDs []int64
}
type args struct {
data []byte
}
tests := []struct {
name string
fields fields
args args
}{
{
name: "zero_value",
fields: fields{},
args: args{
data: []byte(`{}`),
},
},
{
name: "basic_values",
fields: fields{
NextCursor: 0,
PreviousCursor: 0,
IDs: []int64{1, 2, 3},
},
args: args{
data: []byte(`{"ids": [1, 2, 3], "next_cursor": 0, "previous_cursor": 0}`),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cursored := &CursoredResponse{}
cursored.From(&tt.args.data)
if !reflect.DeepEqual(cursored.IDs, tt.fields.IDs) {
t.Fatalf("got: %v, wanted: %v", cursored.IDs, tt.fields.IDs)
}
if cursored.NextCursor != tt.fields.NextCursor {
t.Fatalf("got: %v, wanted: %v", cursored.NextCursor, tt.fields.NextCursor)
}
if cursored.PreviousCursor != tt.fields.PreviousCursor {
t.Fatalf("got: %v, wanted: %v", cursored.PreviousCursor, tt.fields.PreviousCursor)
}
})
}
}
package twitter_api
import (
"fmt"
"github.com/buger/jsonparser"
"github.com/pkg/errors"
)
var ErrNoData = errors.New("no data provided")
type Error struct {
Message string `json:"message"`
Code int `json:"code"`
}
// ErrorResponse example:
// {"errors":[{"message":"Sorry, that page does not exist","code":34}]}
type ErrorResponse struct {
Errors []Error `json:"errors"`
// for formatting error string
method string
url string
statusCode int
body []byte
}
func (resp *ErrorResponse) Error() string {
return fmt.Sprintf("received status %d with %s from %s %s", resp.statusCode, string(resp.body), resp.method, resp.url)
}
func (resp *ErrorResponse) First() (Error, bool) {
if len(resp.Errors) == 0 {
return Error{}, false
}
return resp.Errors[0], true
}
func (resp *ErrorResponse) From(data []byte) error {
if data == nil {
return ErrNoData
}
resp.Errors = []Error{}
// {"errors": ...}
value, _, _, err := jsonparser.Get(data, "errors")
if err != nil {
return err
}
// {"errors": [...]}
_, err = jsonparser.ArrayEach(value, func(value []byte, dataType jsonparser.ValueType, _ int, err error) {
if err != nil {
panic(err)
}
resp.Errors = append(resp.Errors, parseError(value))
})
if err != nil {
return err
}
return nil
}
func parseError(value []byte) Error {
var errorObj Error
// {"message": ..., "code": ...}
jsonparser.EachKey(value, func(idx int, value []byte, dataType jsonparser.ValueType, err error) {
if err != nil {
panic(err)
}
switch idx {
case 0:
errorObj.Message, err = jsonparser.ParseString(value)
if err != nil {
panic(err)
}
case 1:
code, err := jsonparser.ParseInt(value)
if err != nil {
panic(err)
}
errorObj.Code = int(code)
}
}, [][]string{
{"message"},
{"code"},
}...)
return errorObj
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment