Go has a standard package encoding/json
with two major
functions json.Encoder()
and json.Decoder()
which encode and decode to a stream, and json.Unmarshal()
and json.Marshal()
which unmarshal and marshal to and
from a byte array.
From the Go JSON package specification, marshalling uses the following type-dependent default encodings:
There is a JSON schema to Go package Go Jsonschema . Applying this to the person schema by
./gojsonschema-linux-amd64 -p person -o src/person/Person.go person.schema.json
This will need to be built by
go build src/person/Person.go
where the current directory is in GOPATH
.
The resultant Person.go
is
Person.go:
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package person
import "fmt"
import "encoding/json"
type Person struct {
// Email corresponds to the JSON schema field "email".
Email []PersonEmailElem `json:"email"`
// Name corresponds to the JSON schema field "name".
Name PersonName `json:"name"`
}
type PersonEmailElem struct {
// Address corresponds to the JSON schema field "address".
Address *string `json:"address,omitempty"`
// Kind corresponds to the JSON schema field "kind".
Kind *string `json:"kind,omitempty"`
}
type PersonName struct {
// Family corresponds to the JSON schema field "family".
Family *string `json:"family,omitempty"`
// Personal corresponds to the JSON schema field "personal".
Personal *string `json:"personal,omitempty"`
}
// UnmarshalJSON implements json.Unmarshaler.
func (j *Person) UnmarshalJSON(b []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
if v, ok := raw["email"]; !ok || v == nil {
return fmt.Errorf("field email: required")
}
if v, ok := raw["name"]; !ok || v == nil {
return fmt.Errorf("field name: required")
}
type Plain Person
var plain Plain
if err := json.Unmarshal(b, &plain); err != nil {
return err
}
*j = Person(plain)
return nil
}
func (name PersonName) String() string {
return fmt.Sprintf("Name{Personal: %s, Family: %s}", *name.Personal, *name.Family)
}
func (email PersonEmailElem) String() string {
return fmt.Sprintf("Email{Address: %s, Kind: %s}", *email.Address, *email.Kind)
}
It defines the types
Person
, PersonEmailElem
and PersonName
The JSON encoder maps Go type names to JSON field names. Sometimes, the
best Go name for a type may not match the corresponding JSON name.
Go allows for a type annotation to control the mapping by
`json:"string"`
.
For example, a Person
struct may be defined as
type Person struct {
// Email corresponds to the JSON schema field "email".
Email []PersonEmailElem `json:"email"`
// Name corresponds to the JSON schema field "name".
Name PersonName `json:"name"`
}
Notice that the string fields are pointers, not the strings themselves.
There is a standard way of pretty printing objects using the
fmt
package:
fmt.Printf("%v\n", person)
Unfortunately, for pointer types, it prints the pointer value, not the value
pointed at.
As discussed at
golang how to print struct value with pointer
This can be remedied by implementing the Stringer
interface.
Basically, it means adding to Person.go
the following:
func (name PersonName) String() string {
return fmt.Sprintf("Name{Personal: %s, Family: %s}", *name.Personal, *name.Family)
}
func (email PersonEmailElem) String() string {
return fmt.Sprintf("Email{Address: %s, Kind: %s}", *email.Address, *email.Kind)
}
But Person.go
is automatically generated, so any addition
like this will get overwritten on a regeneration. If I get time, I
will fork the project to automatically generate this extra code.
The client constructs a Person
object using the generated API.
It then connects as with the TCP example and sends the JSON serialised
data to the server.
The client is PersonClient.go:
/* JSON PersonClient
*/
package main
import (
"person"
"encoding/json"
"fmt"
"net"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprint(os.Stderr, "Usage: ", os.Args[0], " host:port\n")
os.Exit(1)
}
service := os.Args[1]
// build a Person
toaddr := func(s string) *string { return &s}
name := person.PersonName{Family: toaddr("Newmarch"),
Personal: toaddr("Jan")}
email1 := person.PersonEmailElem{Kind: toaddr("home"),
Address: toaddr("jan@newmarch.name")}
email2 := person.PersonEmailElem{Kind: toaddr("work"),
Address: toaddr("j.newmarch@boxhill.edu.au")}
emails := []person.PersonEmailElem{email1, email2}
person := person.Person{Name: name,
Email: emails}
// the built object
fmt.Printf("%v\n", person)
// get JSON bytes
bytes, _ := json.Marshal(person)
// and send to server
conn, err := net.Dial("tcp", service)
checkError("Dial", err)
fmt.Println("Connected")
conn.Write(bytes)
conn.Close()
os.Exit(0)
}
func checkError(errStr string, err error) {
if err != nil {
fmt.Fprint(os.Stderr, errStr, err.Error())
os.Exit(1)
}
}
The client is run by
go run PersonClient.go localhost:2002
The output on the client side shows the built object:
{[Email{Address: jan@newmarch.name, Kind: home} Email{Address: j.newmarch@boxhill.edu.au, Kind: work}] Name{Personal: Jan, Family: Newmarch}}
Connected
The server acts like the TCP servers discussed earlier. This one just receives a
single packet and then unmarshals it from JSON. Since JSON doesn't include its
type information, an 'empty' Person
object has to be passed
as parameter.
The server is PersonServer.go:
/* JSON PersonServer
*/
package main
import (
"person"
"encoding/json"
"fmt"
"net"
"os"
)
func main() {
service := ":2002"
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
fmt.Println("Connected")
// run as a coroutine
go handleClient(conn)
}
}
func handleClient(conn net.Conn) {
// close connection on exit
defer conn.Close()
var data []byte
data = make([]byte, 2048, 2048)
n, err := conn.Read(data)
if err != nil {
fmt.Println("Disconnecting")
return
}
person := person.Person{}
err = json.Unmarshal(data[:n], &person)
if err != nil {
fmt.Println("Not a person")
return
}
fmt.Printf("%v\n", person)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
The server is run by
go run PersonServer.go
The src
directory containing /person/Person.go
must be eachable from the GOPATH
.
The server will print the object it creates as
{[Email{Address: jan@newmarch.name, Kind: home} Email{Address: j.newmarch@boxhill.edu.au, Kind: work}] Name{Personal: Jan, Family: Newmarch}}
Copyright © Jan Newmarch, jan@newmarch.name
" Network Programming using Java, Go, Python, Rust, JavaScript and Julia"
by
Jan Newmarch
is licensed under a
Creative Commons Attribution-ShareAlike 4.0 International License
.
Based on a work at
https://jan.newmarch.name/NetworkProgramming/
.