(X) The Ultimate Guide To JSON in Go
(X) The Ultimate Guide To JSON in Go
As a language designed for the web, Go provides extensive support for working with
JSON data. JSON (JavaScript Object Notation) is an incredibly popular data exchange
format whose syntax resembles simple JavaScript objects. It’s one of the most common
ways for applications to communicate on the modern web.
As you can see, Marshal() takes a value as input, and returns the encoded JSON as a
slice of bytes on success, or an error if something went wrong.
dat, _ := json.Marshal(`User{
FirstName: "Lane",
BirthYear: 1990,
Email: "example@gmail.com",
}`)
fmt.Println(string(dat))
// prints:
// {"first_name":"Lane","birth_year":1990,"Email":"example@gmail.com"}
Similarly, the json.Unmarshal() function takes some encoded JSON data and a pointer
to a value where the encoded JSON should be written, and returns an error if something
goes wrong.
dat := []byte(`{
"first_name":"Lane",
"birth_year":1990,
"Email":"example@gmail.com"
}`)
user := User{}
err := json.Unmarshal(dat, &user)
if err != nil {
fmt.Println(err)
}
fmt.Println(user)
// prints:
// {Lane 1990 example@gmail.com}
The respondWithError function wraps the respondWithJSON function and always sends
an error message. Now let’s take a look at how to build a full HTTP handler.
Since the json.Marshal and json.Unmarshal function work on the []byte type, it’s
really easy to send those bytes over the wire or write them to disk.
float64 number
string string
You will notice that the float32 and int types are missing. Don’t worry, you can
certainly encode and decode numbers into these types, they just don’t have an explicit
type in the JSON specification. For example, if you encode an integer in JSON, it’s
guaranteed not to have a decimal point. However, if someone converts that JSON value
to a floating-point number before you decode it, you’ll get a runtime error.
It’s rare to encounter an error when marshaling JSON data, but unmarshalling JSON can
often cause errors. Here are some things to watch out for:
Any type conflicts will result in an error. For example, you can’t unmarshal
a string into a int , even if the string value is a stringified number: "speed":
"42"
A floating-point number can’t be decoded into an integer
A null value can’t be decoded into a value that doesn’t have a nil option. For
example, if you have a number field that can be null , you should unmarshal into
a *int
A time.Time can only decode an RFC 3339 string - other kinds of timestamps will
fail
One of the most common scenarios for me is want to encode and decode timestamps in a
different format, usually due to interoperability with another language like JavaScript.
func main() {
g := Group{
ID: "my-id",
CreatedAt: unixTimestamp(time.Unix(1619544689, 0)),
}
dat, _ := json.Marshal(g)
fmt.Println(string(dat))
// prints
// {"id":"my-id","created_at":1619544689}
newG := Group{}
json.Unmarshal(dat, &newG)
fmt.Println(newG)
// prints
// {my-id {0 63755141489 0x1694c0}}
}
The best way to handle this case is to unmarshal the data into
a map[string]interface{}
dat := []byte(`{
"first_name": "lane",
"age": 30
}`)
m := map[string]interface{}{}
json.Unmarshal(dat, &m)
for k, v := range m {
fmt.Printf("key: %v, value: %v\n", k, v)
}
// prints
// key: first_name, value: lane
// key: age, value: 30
I want to point out that map[string]interface{} should only be used when you
absolutely have to. If you have a priori knowledge of the shape of the data, please use
a struct or some other concrete type. Avoid the dynamic typing provided by interfaces
when working with JSON, if you want, you can always use anonymous structs for one-off
usage.
dec := json.NewDecoder(os.Stdin)
enc := json.NewEncoder(os.Stdout)
for {
v := map[string]interface{}{}
if err := dec.Decode(&v); err != nil {
log.Fatal(err)
}
v["id"] = "gopher-man"
if err := enc.Encode(&v); err != nil {
log.Fatal(err)
}
}
You can customize how you want your pretty JSON to be formatted, but if you just want it
to have proper tabs and newlines you can do the following.
fmt.Println(string(json))
// prints
// {
// "Name": "lane",
// "Age": 30
// }
ffjson Readme.md
If you’re thinking about using this library let me give you my quick two cents: I’ve never
actually needed to speed up my JSON performance, it’s never been a bottleneck in my
apps. If you desperately need to increase performance I think this is a great tool to look
into, but don’t add needless dependencies to your code when the benefit they offer isn’t
truly necessary.