Skip to content

Instantly share code, notes, and snippets.

@aurelijusb
Last active September 26, 2017 19:12
Show Gist options
  • Save aurelijusb/5640816026e8b5f0e979a26be1e79ac2 to your computer and use it in GitHub Desktop.
Save aurelijusb/5640816026e8b5f0e979a26be1e79ac2 to your computer and use it in GitHub Desktop.
Go+JSON: examples for presentation

Go+JSON in practice

Working examples for presentation

Usage

For most of the files:

go run FILENAME

If something will require dependencies, check impoert section and do

go get PACKAGE_IN_IMPORT_SECTION

Author

Aurelijus Banelis

{
"items": [
{
"version": "v3",
"title": "Google Analytics API",
"description": "Views and manages your Google Analytics data.",
"documentationLink": "https://developers.google.com/analytics/"
},
{
"title": "Google Analytics API",
"description": "Views and manages your Google Analytics data.",
"documentationLink": "https://developers.google.com/analytics/",
"version": "v2.4"
}
]
}
package main
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
"net/http"
"io/ioutil"
"time"
"encoding/json"
"fmt"
)
type (
FirstLastName string
Date string
Url string
HTML string
Address string
Company struct {
Name string `json:"name"`
Url Url `json:"url"`
}
Topic struct {
Title string `json:"title"`
Description HTML `json:"description"`
}
Speaker struct {
Name FirstLastName `json:"name"`
Topic Topic `json:"topic"`
Company *Company `json:"company,omitempty"`
}
Place struct {
Name string `json:"name"`
Address Address `json:"address"`
}
Meetup struct {
Session *uint `json:"session"`
Episode *uint `json:"episode"`
Name string `json:"name"`
Speakers []Speaker
Date Date `json:"date"`
Venue Place `json:"venue"`
Sponsor Company `json:"sponsor"`
Description HTML `json:"description"`
}
Meetups []Meetup
)
var _ = Describe("Examples of usefulnes of running tests", func() {
It("Should be easy to copy expected values from curl response", func() {
response := readAll("https://www.googleapis.com/discovery/v1/apis?name=analytics&fields=items(description%2CdocumentationLink%2Ctitle%2Cversion)")
// Note "version" and "title" are not ordered
expected := `{
"items": [
{
"title": "Google Analytics API",
"description": "Views and manages your Google Analytics data.",
"documentationLink": "https://developers.google.com/analytics/",
"version": "v2.4"
},
{
"version": "v3",
"title": "Google Analytics API",
"description": "Views and manages your Google Analytics data.",
"documentationLink": "https://developers.google.com/analytics/"
}
]
}`
Expect(response).Should(MatchJSON(expected))
})
It("Is hard to write tests with deep unless you have good IDE and write by position", func() {
meetups := Meetups{
Meetup{
Name: "Golang Meetup S02E01",
Session: intPtr(2),
Episode: intPtr(1),
Speakers:[]Speaker{
{
FirstLastName("Aurelijus Banelis"),
Topic{
"JSON+Go in practice",
HTML("Practical tips and tricks ..."),
},
&Company{
"NFQ",
Url("http://www.nfq.lt/"),
},
},
{
FirstLastName("Valdas Petrulis"),
Topic{
"NATS pub-sub daemon packed inside your application ",
HTML("Here at www.mysterium.network ..."),
},
&Company{
"MysteriumNetwork",
Url("http://www.mysterium.network/"),
},
},
},
Date: Date("2017-09-27"),
Description: HTML("Arriving: Call Povilas if you'll face any problems reaching the place."),
Sponsor: Company{
"Uber",
Url("https://www.uber.com/en-LT/"),
},
Venue: Place{
Name: "Uber",
Address: Address("Lvovo g. 25, Vilnius"),
},
},
Meetup{
Name: "Golang Meetup #5",
Speakers:[]Speaker{
{
"Martynas Pumputis",
Topic{
"Go and Linux Namespaces: Better Don't Mix",
"This talk is about discovering Go runtime limitations which make us reconsider its adoption within container software",
},
&Company{
"Weaveworks",
"https://www.weave.works/",
},
},
{
"Mike Kabischev",
Topic{
"Instrumenting Go application",
"Instrumentation ...",
},
nil,
},
{
"Max Chechel",
Topic{
"Code generation in go",
"Why do we need to generate Go ...",
},
nil,
},
},
Date: "2017-05-24",
Description: "Arriving: Call Povilas if you'll face any problems reaching the place.",
Sponsor: Company{
"Uber",
"https://www.uber.com/en-LT/",
},
Venue: Place{
Name: "Uber",
Address: "Lvovo g. 25, Vilnius",
},
},
}
Expect(nextSpeaker(mockedClock, meetups)).Should(Equal("Aurelijus Banelis"))
})
It("JSON can be used for deep structures and no fear of modification by pointers", func() {
meetups := Meetups{}
input := `[
{
"name": "Golang Meetup S02E01",
"session": 2,
"episode": 1,
"Speakers": [
{
"name": "Aurelijus Banelis",
"topic": {
"title": "JSON+Go in practice",
"description": "Practical tips and tricks ..."
},
"company": {
"name": "NFQ",
"url": "http://www.nfq.lt/"
}
},
{
"name": "Valdas Petrulis",
"topic": {
"title": "NATS pub-sub daemon packed inside your application ",
"description": "Here at www.mysterium.network ..."
},
"company": {
"name": "MysteriumNetwork",
"url": "http://www.mysterium.network/"
}
}
],
"date": "2017-09-27",
"venue": {
"name": "Uber",
"address": "Lvovo g. 25, Vilnius"
},
"sponsor": {
"name": "Uber",
"url": "https://www.uber.com/en-LT/"
},
"description": "Arriving: Call Povilas if you'll face any problems reaching the place."
},
{
"name": "Golang Meetup #5",
"Speakers": [
{
"name": "Martynas Pumputis",
"topic": {
"title": "Go and Linux Namespaces: Better Don't Mix",
"description": "This talk is about discovering Go runtime limitations which make us reconsider its adoption within container software"
},
"company": {
"name": "Weaveworks",
"url": "https://www.weave.works/"
}
},
{
"name": "Mike Kabischev",
"topic": {
"title": "Instrumenting Go application",
"description": "Instrumentation ..."
}
},
{
"name": "Max Chechel",
"topic": {
"title": "Code generation in go",
"description": "Why do we need to generate Go ..."
}
}
],
"date": "2017-05-24",
"venue": {
"name": "Uber",
"address": "Lvovo g. 25, Vilnius"
},
"sponsor": {
"name": "Uber",
"url": "https://www.uber.com/en-LT/"
},
"description": "Arriving: Call Povilas if you'll face any problems reaching the place."
}
]`
json.Unmarshal([]byte(input), &meetups)
Expect(nextSpeaker(mockedClock, meetups)).Should(Equal("Aurelijus Banelis"))
})
It("Fmt prints badly deep structures of pointers", func() {
meetups := Meetups{
Meetup{
Name: "Golang Meetup S02E01",
Session: intPtr(2),
Episode: intPtr(1),
Speakers: []Speaker{
{
Company: &Company{
Name: "NFQ",
},
},
},
},
}
representation := fmt.Sprintf("%#v", meetups)
// Representation: test.Meetups{test.Meetup{Session:(*uint)(0xc42028c5d0), Episode:(*uint)(0xc42028c5d8) ...
Expect(representation).ShouldNot(BeEmpty())
})
})
func intPtr(value uint) *uint {
return &value
}
var mockedClock, _ = time.Parse(time.RFC3339, "2017-09-27T19:10:00+03:00")
func nextSpeaker(new time.Time, meetups Meetups) string {
// Skipping real implementation just for the sake of example
return string(meetups[0].Speakers[0].Name)
}
func readAll(url string) string {
resp, err := http.Get(url)
defer resp.Body.Close()
if err != nil {
panic(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
return string(body)
}
func TestGoJson(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Go+Json Example Suite")
}
default:
golint *.go
go vet *.go
go fmt *.go
run:
go run naming.go
go run naming2.go
go run naming-unmarshal.go
go run naming-polymorphism.go
package main
// THIS IS AUTO GENERATED FILE - DO NOT EDIT!
// SomeGeneratedDTO type
type SomeGeneratedDTO struct {
ID CommonGenerated.ID `json:"id"`
ParentID CommonGenerated.ID `json:"parentId"`
Title CommonGenerated.Translation `json:"title"`
}
package main
import (
"encoding/json"
"fmt"
)
// Main structure
type polymorphic struct {
Type string `json:"type"`
Attributes iAttribute `json:"attributes"`
}
type iAttribute interface {
Type() string
}
// Image type
var _ iAttribute = (*image)(nil)
func (i *image) Type() string {
return "image"
}
type image struct {
Path string `json:"path"`
}
// Meetup type
var _ iAttribute = (*meetup)(nil)
type meetup struct {
Language string `json:"language"`
Date string `json:"date"`
}
func (i *meetup) Type() string {
return "meetup"
}
// Testing example
func main() {
data := []polymorphic{
{
Type: "image",
Attributes: &image{
Path: "http://tny.im/ajb",
},
},
{
Type: "meetup",
Attributes: &meetup{
Language: "go",
Date: "2017-09-27",
},
},
}
api, err := json.MarshalIndent(&data, "", " ")
if err != nil {
panic(err.Error())
}
fmt.Printf("%s\n", api)
}
package main
import (
"encoding/json"
"fmt"
"github.com/segmentio/go-camelcase"
"reflect"
)
type structureDynamic struct {
NetworkID string
EndpointID string
Gateway string
IPAddress string
IPPrefixLen int
}
func (s *structureDynamic) MarshalJSON() ([]byte, error) {
keyValues := map[string]interface{}{}
value := reflect.ValueOf(s).Elem()
for i := 0; i < value.NumField(); i++ {
valueField := value.Field(i)
typeField := value.Type().Field(i)
name := camelcase.Camelcase(typeField.Name)
keyValues[name] = valueField.Interface()
}
return json.Marshal(keyValues)
}
func main() {
data := structureDynamic{
NetworkID: "b5a465c4ddf1",
EndpointID: "5483c7bbf04",
Gateway: "172.20.0.1",
IPAddress: "172.20.0.4",
IPPrefixLen: 16,
}
api, err := json.MarshalIndent(&data, "", " ")
if err != nil {
panic(err.Error())
}
fmt.Printf("%s\n", api)
}
// Adapted from https://gist.github.com/drewolson/4771479
package main
import (
"encoding/json"
"fmt"
)
type structureGo struct {
NetworkID string
EndpointID string
Gateway string
IPAddress string
IPPrefixLen int
}
func main() {
data := structureGo{
NetworkID: "b5a465c4ddf1",
EndpointID: "5483c7bbf04",
Gateway: "172.20.0.1",
IPAddress: "172.20.0.4",
IPPrefixLen: 16,
}
api, err := json.MarshalIndent(&data, "", " ")
if err != nil {
panic(err.Error())
}
fmt.Printf("%s\n", api)
}
// Go lint: struct field NetworkID should be NetworkID
{
"NetworkID": "b5a465c4ddf1",
"EndpointID": "5483c7bbf04",
"Gateway": "172.20.0.1",
"IPAddress": "172.20.0.4",
"IPPrefixLen": 16
}
package main
import (
"encoding/json"
"fmt"
)
type structureJs struct {
NetworkID string `json:"networkId"`
EndpointID string `json:"endpointId"`
Gateway string `json:"gateway"`
IPAddress string `json:"ipAddress"`
IPPrefixLen int `json:"ipPrefixLen"`
}
func main() {
data := structureJs{
NetworkID: "b5a465c4ddf1",
EndpointID: "5483c7bbf04",
Gateway: "172.20.0.1",
IPAddress: "172.20.0.4",
IPPrefixLen: 16,
}
api, err := json.MarshalIndent(&data, "", " ")
if err != nil {
panic(err.Error())
}
fmt.Printf("%s\n", api)
}
{
"networkId": "b5a465c4ddf1",
"endpointId": "5483c7bbf04",
"gateway": "172.20.0.1",
"IpAddress": "172.20.0.4",
"IpPrefixLen": 16
}
[
{
"type": "image",
"attributes": {
"path": "http://tny.im/ajb"
}
},
{
"type": "meetup",
"attributes": {
"language": "Go",
"date": "2017-09-27"
}
}
]
package main
import (
"encoding/json"
"fmt"
"github.com/juju/errors"
)
type (
Image struct {
Path string `json:"path"`
}
Meetup struct {
Language string `json:"language"`
Date string `json:"date"`
}
IAttribute interface {
Type() string
}
Element struct {
attribute IAttribute
}
)
func (i *Element) UnmarshalJSON(data []byte) error {
intermediate := struct {
Type string `json:"type"`
Attributes json.RawMessage `json:"attributes"`
}{}
err := json.Unmarshal(data, &intermediate)
if err != nil {
return errors.Annotate(err, "Invalid Element")
}
switch intermediate.Type {
case "image":
img := Image{}
err = json.Unmarshal(intermediate.Attributes, &img)
if err == nil {
i.attribute = &img
}
break
case "meetup":
meetup := Meetup{}
err = json.Unmarshal(intermediate.Attributes, &meetup)
if err == nil {
i.attribute = &meetup
}
break
default:
err = fmt.Errorf("Unexpected element type %s", intermediate.Type)
}
return errors.Trace(err)
}
func (p *Image) Type() string {
return "image"
}
func (p Meetup) Type() string {
return "meetup"
}
type Elements []Element
type GroupedElements map[string]Elements
func main() {
var elements []Element
err := json.Unmarshal([]byte(`
[
{
"type": "image",
"attributes": {
"path": "http://tny.im/ajb"
}
},
{
"type": "meetup",
"attributes": {
"language": "Go",
"date": "2017-09-27"
}
}
]
`), &elements)
if err != nil {
fmt.Printf("Validation: %s\n", err.Error())
}
fmt.Printf("Data: %#v\n", elements)
var grouped GroupedElements
err = json.Unmarshal([]byte(`
{
"images": [
{
"type": "image",
"attributes": {
"path": "http://tny.im/ajb"
}
}
],
"meetups": [
{
"type": "meetup",
"attributes": {
"language": "Go",
"date": "2017-09-27"
}
}
]
}
`), &grouped)
if err != nil {
fmt.Printf("Validation: %s\n", err.Error())
}
fmt.Printf("Data: %#v\n", grouped)
var grouped2 GroupedElements
err = json.Unmarshal([]byte(`
{
"images": [
{
"type": "image",
"attributes": {
"path": "http://tny.im/ajb"
}
}
],
"meetups": null
}
`), &grouped2)
if err != nil {
fmt.Printf("Validation: %s\n", err.Error())
}
fmt.Printf("Data: %#v\n", grouped2)
}
{
"images": [
{
"type": "image",
"attributes": {
"path": "http://tny.im/ajb"
}
}
],
"meetups": null
}
{
"images": [
{
"type": "image",
"attributes": {
"path": "http://tny.im/ajb"
}
}
],
"meetups": [
{
"type": "meetup",
"attributes": {
"language": "Go",
"date": "2017-09-27"
}
}
]
}
package main
import (
"encoding/json"
"fmt"
)
type LocationNumber uint64
type LocationData struct {
Number LocationNumber `json:"number"`
}
func (i *LocationNumber) UnmarshalJSON(data []byte) error {
var longInteger uint64
var longFloat float64
errLong := json.Unmarshal(data, &longInteger)
errFloat := json.Unmarshal(data, &longFloat)
if errLong != nil && errFloat == nil {
*i = LocationNumber(longFloat)
return nil
}
if errLong != nil {
return errLong
}
*i = LocationNumber(longInteger)
return nil
}
func main() {
// 1.0e+19
data1 := LocationData{}
err := json.Unmarshal([]byte(`{"number": 100}`), &data1)
if err != nil {
fmt.Printf("Validation: %s\n", err.Error())
}
fmt.Printf("Data: %#v\n", data1)
data2 := LocationData{}
err = json.Unmarshal([]byte(`{"number": 1.0e+8}`), &data2)
if err != nil {
fmt.Printf("Validation: %s\n", err.Error())
}
fmt.Printf("Data: %#v %d\n", data2, data2.Number)
}
package main
import (
"encoding/json"
"fmt"
"sort"
)
func main() {
set1 := []string{"a", "b", "c"}
set2 := map[string]string{"a": "a", "b": "b", "c": "c"}
sort.Strings(set1)
setJson1, _ := json.MarshalIndent(set1, "", " ")
setJson2, _ := json.MarshalIndent(set2, "", " ")
fmt.Printf("Set as array:\n%s\n", setJson1)
fmt.Printf("Set as objects:\n%s\n", setJson2)
}
package main
import (
"encoding/json"
"fmt"
"gopkg.in/go-playground/validator.v9"
"strings"
)
type DataExample struct {
Email string `json:"email" validate:"email,isExampleDomain"`
Gln string `json:"gln" validate:"numeric,len=13"`
}
func main() {
validate := validator.New()
validate.RegisterValidation("isExampleDomain", func(fl validator.FieldLevel) bool {
return strings.HasSuffix(fl.Field().String(), "@example.com")
})
data1 := DataExample{}
json.Unmarshal([]byte(`{"email":"aurelijus@example.com", "gln":"1234567890123"}`), &data1)
err := validate.Struct(data1)
if err == nil {
fmt.Printf("Data: %#v\n", data1)
}
data2 := DataExample{}
json.Unmarshal([]byte(`{"email":"aurelijus@example.com", "gln":"123"}`), &data2)
err = validate.Struct(data2)
if err != nil {
fmt.Printf("Validation: %s\n", err.Error())
}
data3 := DataExample{}
json.Unmarshal([]byte(`{"email":"aurelijus@google.com", "gln":"1234567890123"}`), &data3)
err = validate.Struct(data3)
if err != nil {
fmt.Printf("Validation: %s\n", err.Error())
}
}
package main
import (
"encoding/json"
"fmt"
"github.com/juju/errors"
"strings"
"time"
)
// Validation with builtin JSON types
type Data struct {
ID string `json:"id"`
Location uint64 `json:"location"`
}
// Validation as custom type example
type ID string
type Data2 struct {
ID ID `json:"id"`
Location uint64 `json:"location"`
}
func (i *ID) UnmarshalJSON(data []byte) error {
var parsed string = ""
err := json.Unmarshal(data, &parsed)
errorMessage := "Invalid input %s. Expected ID need to be 6 digits uppercase string"
if err != nil {
return errors.Annotatef(err, errorMessage)
}
if len(parsed) != 6 || strings.ToUpper(parsed) != parsed {
return fmt.Errorf(errorMessage, data)
}
// Common mistake to forgot to update and not to confuse pointer and pointed value
*i = ID(parsed)
return nil
}
// Date type example
type Date time.Time
func (d *Date) UnmarshalJSON(data []byte) error {
var parsed string = ""
err := json.Unmarshal(data, &parsed)
errorMessage := "Invalid input %s. Expected Date in format 2006-01-02"
if err != nil {
return errors.Annotatef(err, errorMessage, data)
}
var parsedTime time.Time = time.Now()
err = json.Unmarshal([]byte(`"`+parsed+`T00:00:00Z"`), &parsedTime)
if err != nil {
return errors.Annotatef(err, errorMessage, data)
}
// Common mistake to forgot to update and not to confuse pointer and pointed value
*d = Date(parsedTime)
return nil
}
func (d Date) MarshalJSON() ([]byte, error) {
date := time.Time(d).Format("2006-01-02")
return json.Marshal(&date)
}
type DateExample struct {
Date Date `json:"date"`
}
// Manual testing
func main() {
data := &Data{}
err := json.Unmarshal([]byte(`{"id":"testas", "Location":"Vilnius"}`), &data)
fmt.Printf("Validation error: %s\n", err.Error())
data2 := &Data2{}
err = json.Unmarshal([]byte(`{"id":"testas", "Location":123456}`), &data2)
if err != nil {
fmt.Printf("Validation error: %s\n", err.Error())
}
data2valid := &Data2{}
err = json.Unmarshal([]byte(`{"id":"ABCDEF", "Location":123456}`), &data2valid)
fmt.Printf("DateExample %#v\n", data2valid)
data3 := &DateExample{}
err = json.Unmarshal([]byte(`{"date":"2017 September 27th"}`), &data3)
if err != nil {
fmt.Printf("Validation error: %s\n", err.Error())
}
data3valid := &DateExample{}
err = json.Unmarshal([]byte(`{"date":"2017-09-27"}`), &data3valid)
fmt.Printf("DateExample %#v\n", data3)
jsonData3, _ := json.Marshal(data3valid)
fmt.Printf("DateExample %s\n", jsonData3)
}
{"number": 1.0e+8}
{
"set": {
"a": "a",
"b": "b",
"c": "c"
}
}
package main
import (
"encoding/json"
"github.com/ant0ine/go-json-rest/rest"
"io/ioutil"
"log"
"net/http"
"strings"
)
type speakerLatest struct {
Name string `json:"name"`
Topic string `json:"topic"`
LinkedIn string `json:"linkedIn"`
}
func writeValidationFailed(message string, w rest.ResponseWriter) {
w.WriteHeader(http.StatusBadRequest)
var errorResponse = struct {
Error string `json:"error"`
}{message}
w.WriteJson(&errorResponse)
}
func writeSystemError(err error, w rest.ResponseWriter) {
w.WriteHeader(http.StatusInternalServerError)
var errorResponse = struct {
Error string `json:"error"`
}{err.Error()}
w.WriteJson(&errorResponse)
}
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Post("/speaker", func(w rest.ResponseWriter, req *rest.Request) {
// Request is read only once
payload, err := ioutil.ReadAll(req.Body)
if err != nil {
writeSystemError(err, w)
return
}
compatiblity := struct {
HomePage string `json:"homePage"`
}{}
fields := map[string]json.RawMessage{}
latest := speakerLatest{}
err1 := json.Unmarshal(payload, &latest)
err2 := json.Unmarshal(payload, &compatiblity)
err3 := json.Unmarshal(payload, &fields)
if err1 != nil {
writeSystemError(err1, w)
return
}
if err2 != nil {
writeSystemError(err2, w)
return
}
if err3 != nil {
writeSystemError(err3, w)
return
}
linkedInPrefix := "https://www.linkedin.com/in/"
if _, exists := fields["linkedIn"]; !exists && strings.HasPrefix(compatiblity.HomePage, linkedInPrefix) {
latest.LinkedIn = (compatiblity.HomePage)[len(linkedInPrefix):]
}
if _, exists := fields["topic"]; !exists {
writeValidationFailed("topic property must be provided", w)
return
}
if strings.TrimSpace(latest.Topic) == "" {
writeValidationFailed("topic property cannot be empty", w)
return
}
// Assuming everything was ok
w.WriteHeader(201)
w.WriteJson(&latest)
}),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8082", api.MakeHandler()))
}
// Usage:
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis"}' http://127.0.0.1:8082/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice"}' http://127.0.0.1:8082/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://aurelijus.banelis.lt"}' http://127.0.0.1:8082/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://www.linkedin.com/in/aurelijusbanelis"}' http://127.0.0.1:8082/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8082/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8082/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8082/speaker
package main
import (
"encoding/json"
"fmt"
)
type optionalPointers struct {
Attributes map[string]string
}
func main() {
defaultValues, _ := json.Marshal(&optionalPointers{})
defaultInitialised, _ := json.Marshal(&optionalPointers{map[string]string{}})
normalValues, _ := json.Marshal(&optionalPointers{map[string]string{"field": "value"}})
fmt.Printf("%s\n", defaultValues)
fmt.Printf("%s\n", defaultInitialised)
fmt.Printf("%s\n", normalValues)
payload := &struct{ Attributes map[string]string }{}
json.Unmarshal([]byte("{}"), &payload)
payload.Attributes["new"] = "value"
}
package main
import (
"github.com/ant0ine/go-json-rest/rest"
"log"
"net/http"
"strings"
)
type speakerData struct {
Name string `json:"name"`
Topic *string `json:"topic,omitempty"`
HomePage *string `json:"homePage,omitempty"`
LinkedIn string `json:"linkedIn,omitempty"`
}
func writeInvalid(message string, w rest.ResponseWriter) {
w.WriteHeader(http.StatusBadRequest)
var errorResponse = struct {
Error string `json:"error"`
}{message}
w.WriteJson(&errorResponse)
}
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Post("/speaker", func(w rest.ResponseWriter, req *rest.Request) {
markerForNotProvided := "\x00"
data := speakerData{
Name: markerForNotProvided,
}
req.DecodeJsonPayload(&data)
// Using pointer to distinguish provided and not provided field
if data.Topic == nil || strings.TrimSpace(*data.Topic) == "" {
writeInvalid("Old API is deprecated. Please provide topic", w)
return
}
// Too many pointers are error prone
linkedInPrefix := "https://www.linkedin.com/in/"
if data.HomePage != nil && strings.HasPrefix(*data.HomePage, linkedInPrefix) {
data.LinkedIn = (*data.HomePage)[len(linkedInPrefix):]
data.HomePage = nil
}
if data.HomePage != nil && data.LinkedIn == "" {
writeInvalid("Old API is deprecated. Please provide linkedIn instead of homePage", w)
return
}
// Too many optional fields increase the number of checks
if data.LinkedIn == "" {
writeInvalid("Old API is deprecated. Please provide linkedIn field", w)
return
}
// One of the hackish way to solve default values is to provide uncommon value before
if data.Name == markerForNotProvided {
writeInvalid("Validation failed. Please provide name field", w)
return
}
if data.Name == "" {
writeInvalid("Validation failed: name field cannot be empty", w)
return
}
// Assuming everything was ok
w.WriteHeader(201)
w.WriteJson(&data)
}),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8081", api.MakeHandler()))
}
// Usage:
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis"}' http://127.0.0.1:8081/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice"}' http://127.0.0.1:8081/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://aurelijus.banelis.lt"}' http://127.0.0.1:8081/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://www.linkedin.com/in/aurelijusbanelis"}' http://127.0.0.1:8081/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8081/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8081/speaker
// curl -XPOST -H 'Content-type: application/json' -d '{"name":"", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8081/speaker
type a struct {
A *[]map[string]string
}
package main
import (
"errors"
"github.com/ant0ine/go-json-rest/rest"
"log"
"net/http"
"strings"
)
// Input models
type speaker0_0_1 struct {
Name string `json:"name"`
}
type speaker0_1_0 struct {
Name string `json:"name"`
Topic string `json:"topic"`
}
type speaker0_2_0 struct {
Name string `json:"name"`
Topic string `json:"topic"`
HomePage string `json:"homePage"`
}
type speaker0_3_0 struct {
Name string `json:"name"`
Topic string `json:"topic"`
LinkedIn string `json:"linkedIn"`
}
// Controllers
func sepakerController0_0_1(w rest.ResponseWriter, req *rest.Request) {
writeBadRequest("API deprecated. Use /v0.3.0/speaker", w)
}
func speakerController0_1_0(w rest.ResponseWriter, req *rest.Request) {
writeBadRequest("API deprecated. Use /v0.3.0/speaker", w)
}
func speakerController0_2_0(w rest.ResponseWriter, req *rest.Request) {
decodeSpeaker(&speaker0_2_0{}, req, w, meetupController)
}
func speakerController0_3_0(w rest.ResponseWriter, req *rest.Request) {
decodeSpeaker(&speaker0_3_0{}, req, w, meetupController)
}
// Converters to domain objects
type speakerInputToDomain interface {
ToDomainObject() (speaker, error)
}
func (s *speaker0_2_0) ToDomainObject() (speaker, error) {
linkedinPrefix := "https://www.linkedin.com/in/"
if strings.HasPrefix(s.HomePage, linkedinPrefix) {
return speaker{
Name: s.Name,
Topic: s.Topic,
LinkedIn: s.HomePage[len(linkedinPrefix):],
}, nil
}
return speaker{}, errors.New("Only Linkedin usrs are supported. Use /v0.3.0/speaker")
}
func (s *speaker0_3_0) ToDomainObject() (speaker, error) {
return speaker{
Name: s.Name,
Topic: s.Topic,
LinkedIn: s.LinkedIn,
}, nil
}
// Domain objects
type speaker struct {
Name string
Topic string
LinkedIn string
}
// Domain logic
func meetupController(speaker speaker, w rest.ResponseWriter) {
w.WriteHeader(201)
w.WriteJson(&speaker)
}
// Representation helper functions
func decodeSpeaker(speaker speakerInputToDomain, req *rest.Request, w rest.ResponseWriter, controller func(speaker, rest.ResponseWriter)) {
if err := req.DecodeJsonPayload(&speaker); err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Fatal(err)
return
}
latest, err := speaker.ToDomainObject()
if err != nil {
writeBadRequest(err.Error(), w)
return
}
controller(latest, w)
}
type errorResponse struct {
Error string `json:"error"`
}
func writeBadRequest(message string, w rest.ResponseWriter) {
w.WriteHeader(http.StatusBadRequest)
w.WriteJson(&errorResponse{message})
}
// Initiating server
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Post("/v0.0.1/speaker", sepakerController0_0_1),
rest.Post("/v0.1.0/speaker", speakerController0_1_0),
rest.Post("/v0.2.0/speaker", speakerController0_2_0),
rest.Post("/v0.3.0/speaker", speakerController0_3_0),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
// Usage:
// curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis"}' http://127.0.0.1:8080/v0.0.1/speaker
// curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice"}' http://127.0.0.1:8080/v0.1.0/speaker
// curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://aurelijus.banelis.lt"}' http://127.0.0.1:8080/v0.2.0/speaker
// curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://www.linkedin.com/in/aurelijusbanelis"}' http://127.0.0.1:8080/v0.2.0/speaker
// curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8080/v0.3.0/speaker
{"topic":""}
{"Attributes":{}}
{"Attributes":{"field":"value"}}
{}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment