添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Tutorial

How To Use JSON in Go

Published on March 28, 2022
author author
How To Use JSON in Go

The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.

Introduction

Go program checking if a user has access to another program, a JavaScript program getting a list of past orders to display on a website, or a Rust program reading test results from a file, programs need a way to provide other programs with data. However, many programming languages have their own way of storing data internally that other languages don’t understand. To allow these languages to interact, the data needs to be converted to a common format they can all understand. One of these formats, JSON , is a popular way to transmit data over the internet as well as between programs in the same system.

Many modern programming languages include a way to convert data to and from JSON in their standard libraries, and Go does as well. By using the encoding/json package provided by Go, your Go programs will also be able to interact with any other system that can communicate using JSON.

In this tutorial, you will start by creating a program that uses the encoding/json package to encode data from a map into JSON data, then update your program to use a struct type to encode the data instead. After that, you will update your program to decode JSON data into a map before finally decoding the JSON data into a struct type.

Prerequisites

  • How To Install Go tutorial for your operating system.
  • Familiarity with JSON, which you can find in An Introduction to JSON .
  • The ability to use Go struct tags to customize fields on struct types. More information can be found in How To Use Struct Tags in Go .
  • Optionally, be aware of how to create date and time values in Go. You can read more in How To Use Dates and Times in Go .
  • Using a Map to Generate JSON

    json.Marshal function. Marshalling , sometimes also known as serialization , is the process of transforming program data in memory into a format that can be transmitted or saved elsewhere. The json.Marshal function, then, is used to convert Go data into JSON data. The json.Marshal function accepts an interface{} type as the value to marshal to JSON, so any value is allowed to be passed in as a parameter and will return the JSON data as a result. In this section, you will create a program using the json.Marshal function to generate JSON containing various types of data from Go map values, and then print those values to the output.

    Most JSON is represented as an object, with a string key and various other types as values. Because of this, the most flexible way to generate JSON data in Go is by putting data into a map using string keys and interface{} values. The string key can be directly translated to a JSON object key, and the interface{} value allows the value to be any other value, whether it’s a string , an int , or even another map[string]interface{} .

    To get started using the encoding/json package in a program, you’ll need to have a directory for the program. In this tutorial, you’ll use a directory named projects .

    First, make the projects directory and navigate to it:

    1. mkdir projects
    2. cd projects

      Next, make the directory for your project. In this case, use the directory jsondata:

      1. mkdir jsondata
      2. cd jsondata

        Inside the jsondata directory use nano, or your favorite editor, to open the main.go file:

        1. nano main.go

          In the main.go file, you’ll add a main function to run your program. Next, you’ll add a map[string]interface{} value with various keys and types of data. Then, you’ll use the json.Marshal function to marshal the map data into JSON data.

          Add the following lines to main.go:

          main.go
          package main
          import (
          	"encoding/json"
          	"fmt"
          func main() {
          	data := map[string]interface{}{
          		"intValue":    1234,
          		"boolValue":   true,
          		"stringValue": "hello!",
          		"objectValue": map[string]interface{}{
          			"arrayValue": []int{1, 2, 3, 4},
          	jsonData, err := json.Marshal(data)
          	if err != nil {
          		fmt.Printf("could not marshal json: %s\n", err)
          		return
          	fmt.Printf("json data: %s\n", jsonData)
          

          You’ll see in the data variable that each value has a string as the key, but the values for those keys vary. One is an int value, another is a bool value, and one is even another map[string]interface{} value with a []int value inside it.

          When you pass the data variable to json.Marshal, the function will look through all the values you’ve provided and determine which type they are and how to represent them in JSON. If there are any problems in the translation, the json.Marshal function will return an error describing the issue. If the translation is successful, though, the jsonData variable will contain a []byte of the marshalled JSON data. Since a []byte value can be converted to a string value using myString := string(jsonData), or the %s verb in a format string, you can then print the JSON data to the screen using fmt.Printf.

          Save and close the file.

          To see the output of your program, use the go run command and provide the main.go file:

          1. go run main.go

            Your output will look similar to this:

            Output
            json data: {"boolValue":true,"intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}

            In the output, you’ll see that the top-level JSON value is an object represented by curly braces ({}) surrounding it. All of the values you included in data are present. You’ll also see the objectValue’s map[string]interface{} was translated into another JSON object surrounded by {}, and also includes arrayValue inside it with the array value of [1,2,3,4].

            Encoding Times in JSON

            time package.

            Note: For more about Go’s time package, check out the tutorial, How to Use Dates and Times in Go.

            To see this in action, open your main.go file again and add a time.Time value to your data using the time.Date function:

            main.go
            package main
            import (
            	"encoding/json"
            	"fmt"
            	"time"
            func main() {
            	data := map[string]interface{}{
            		"intValue":    1234,
            		"boolValue":   true,
            		"stringValue": "hello!",
            		"dateValue":   time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC),
            		"objectValue": map[string]interface{}{
            			"arrayValue": []int{1, 2, 3, 4},
            

            This update will assign the date March 2, 2022, and the time 9:10:00 AM in the UTC time zone to the dateValue key.

            Once you’ve saved your changes, run your program again with the same go run command as before:

            1. go run main.go

              Your output will look similar to this:

              Output
              json data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}

              This time in the output, you’ll see a dateValue field in the JSON data with the time formatted using the RFC 3339 format, a common format used to convey dates and times as string values.

              Encoding null Values in JSON

              1. Using a Struct to Generate JSON

                statically typed language like Go is that you can use those types to let the compiler check for or enforce consistency in your programs. Go’s encoding/json package allows you to take advantage of this by defining a struct type to represent the JSON data. You can control how the data contained in the struct is translated using struct tags. In this section, you will update your program to use a struct type instead of a map type to generate your JSON data.

                When you use a struct to define JSON data, the field names (not the struct type name itself) you expect to be translated must be exported, meaning they must start with a capital letter, such as IntValue, or the encoding/json package will not be able to access the fields to translate them to JSON. If you don’t use struct tags to control the naming of those fields, the field names will be translated directly as they are on the struct. Using the default names may be what you’d like in your JSON data, depending on how you’d like your data to be formed. If this is the case, you wouldn’t need to add any struct tags. However, many JSON consumers use name formats such as intValue or int_value for their field names, so adding these struct tags will allow you to control how that translation happens.

                For example, say you had a struct with a field called IntValue that you marshalled to JSON:

                type myInt struct {
                	IntValue int
                data := &myInt{IntValue: 1234}
                

                If you marshalled the data variable to JSON using the json.Marshal function, you would end up with the following value:

                {"IntValue":1234}
                

                However, if your JSON consumer expects the field to be named intValue instead of IntValue, you’ll need a way to to tell encoding/json. Since json.Marshal doesn’t know what you expect the field to be named in the JSON data, you’ll tell it by adding a struct tag to the field. By adding a json struct tag to the IntValue field with a value of intValue, you tell json.Marshal it should use the name intValue when generating the JSON data:

                type myInt struct {
                	IntValue int `json:"intValue"`
                data := &myInt{IntValue: 1234}
                

                This time if you marshal the data variable to JSON, the json.Marshal function will see the json struct tag and know to name the field intValue, so you’ll get your expected result:

                {"intValue":1234}
                

                Now, you’ll update your program to use a struct value for your JSON data. You’ll add a myJSON struct type to define your top-level JSON object, as well as a myObject struct to define your inner JSON object for the ObjectValue field. You’ll also add a json struct tag to each of the fields to tell json.Marshal how to name them in the JSON data. You’ll also need to update the data variable assignment to use your myJSON struct, declaring it similar to how you would any other Go struct.

                Open your main.go file and make the following changes:

                main.go
                type myJSON struct { IntValue int `json:"intValue"` BoolValue bool `json:"boolValue"` StringValue string `json:"stringValue"` DateValue time.Time `json:"dateValue"` ObjectValue *myObject `json:"objectValue"` NullStringValue *string `json:"nullStringValue"` NullIntValue *int `json:"nullIntValue"` type myObject struct { ArrayValue []int `json:"arrayValue"` func main() { otherInt := 4321 data := &myJSON{ IntValue: 1234, BoolValue: true, StringValue: "hello!", DateValue: time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC), ObjectValue: &myObject{ ArrayValue: []int{1, 2, 3, 4}, NullStringValue: nil, NullIntValue: &otherInt,

                Many of these changes are similar to the IntValue field name example from before, but some of the changes deserve to be called out specifically. One of them, the ObjectValue field, is using a reference type of *myObject to tell the JSON marshaller to expect either a reference to a myObject value or a nil value. This is how you can define a JSON object that is multiple layers of custom objects deep. If your JSON data required it, you could also have another struct type referenced inside the myObject type, and so on. Using this pattern, you can describe very complex JSON objects using Go struct types.

                Another pair of fields to look at in the above code are NullStringValue and NullIntValue. Unlike StringValue and IntValue, the types of these values are reference types *string and *int. By default, string and int types cannot have a value of nil since their “empty” values are "" and 0. So if you want to represent a field that can be either one type or nil, you need to make it a reference. For example, imagine you have a user questionnaire and you want to be able to represent if a user chose not to answer the question (a null value), or the user didn’t have an answer to the question (a "" value).

                This code also updates the NullIntValue field to assign a value of 4321 to it to show how you might assign a value to a reference type such as *int. In Go you can only create references to primitive types, such as int and string, using variables. So, in order to assign a value to the NullIntValue field, you first assign the value to another variable, otherInt, and then get a reference to that using &otherInt (instead of doing &4321 directly).

                Once you have your updates saved, run your program using go run:

                1. go run main.go

                  Your output will look similar to this:

                  Output
                  json data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullStringValue":null,"nullIntValue":4321}

                  You’ll see this output is the same as when you used a map[string]interface{} value, except this time nullIntValue has a value of 4321 because that’s the value of otherInt.

                  Initially, it may take some extra time to set up your struct values, but once you have them defined, you can use them over and over in your code, and the result will be the same no matter where you use them. You can also update them in one place instead of trying to find every place where a map may be used instead.

                  Go’s JSON marshaller also allows you to control whether a field should be included in the JSON output based on whether the value is empty or not. Sometimes you may have a large JSON object or optional fields you don’t want to be included all the time, so omitting those fields can be useful. Controlling whether a field is omitted when it’s empty or not is done via the omitempty option in the json struct tag.

                  Now, update your program to make the NullStringValue field omitempty and add a new field called EmptyString with the same option:

                  main.go
                  type myJSON struct { NullStringValue *string `json:"nullStringValue,omitempty"` NullIntValue *int `json:"nullIntValue"` EmptyString string `json:"emptyString,omitempty"`

                  Now, when myJSON is marshalled, both the EmptyString and NullStringValue fields will be excluded from the output if their values are empty.

                  After you’ve saved your changes, run your program using go run:

                  1. go run main.go

                    Your output will look similar to this:

                    Output
                    json data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullIntValue":4321}

                    This time in the output, you’ll see the nullStringValue field no longer appears. Since it’s considered empty by having a nil value, the omitempty option excluded it from the output. You’ll also see the new emptyString field isn’t included either. Even though the emptyString value isn’t nil, the default "" value for a string is considered empty so it was excluded as well.

                    In this section, you updated your program to use struct types to generate JSON data with json.Marshal instead of a map type. You also updated your program to omit empty fields from your JSON output.

                    In order for your programs to fit well into the JSON ecosystem, though, you need to do more than just generate JSON data. You’ll also need to be able to read JSON data being sent in response to your requests, or other systems sending requests to you. The encoding/json package also provides a way to decode JSON data into various Go types. In the next section, you’ll update your program to decode a JSON string into a Go map type.

                    Parsing JSON Using a Map

                    raw string literal to allow the declaration to span multiple lines for easier reading. After declaring data as a map[string]interface{}, you pass jsonData and data to json.Unmarshal to unmarshal the JSON data into the data variable.

                    The jsonData variable is being passed to json.Unmarshal as a []byte because the function requires a []byte type and jsonData is initially defined as a string type. This works because a string in Go can be translated to a []byte, and vice versa. The data variable is being passed as a reference because in order for json.Unmarshal to put data into the variable it needs to have a reference to where the variable is being stored in memory.

                    Finally, once the JSON data has been unmarshalled into the data variable, you print it to the screen using fmt.Printf.

                    To run your updated program, save your changes and run the program using go run:

                    1. go run main.go

                      The output will look similar to this:

                      Output
                      json map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:<nil> nullStringValue:<nil> objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!]

                      This time, your output shows the Go side of the JSON translation. You have a map value, with the various fields from the JSON data included. You’ll see that even the null fields from the JSON data show up in the map.

                      Now, because your Go data is in a map[string]interface{}, there’s a little bit of work that needs to go into using the data. You need to get the value from the map using the desired string key value, then you need to make sure the value you received is the one you were expecting because it’s returned to you as an interface{} value.

                      To do this, open the main.go file and update your program to read the dateValue field with the following code:

                      main.go
                      func main() { fmt.Printf("json map: %v\n", data) rawDateValue, ok := data["dateValue"] if !ok { fmt.Printf("dateValue does not exist\n") return dateValue, ok := rawDateValue.(string) if !ok { fmt.Printf("dateValue is not a string\n") return fmt.Printf("date value: %s\n", dateValue)

                      In this update, you use data["dateValue"] to get the rawDateValue as an interface{} type, and use the ok variable to make sure the dateValue field is in the map.

                      Then, you use a type assertion to assert the type of rawDateValue is actually a string value, and assign it to the variable dateValue. After that, you use the ok variable again to make sure the assertion succeeded.

                      Finally, you use fmt.Printf to print dateValue.

                      To run your updated progam again, save your changes and run it using go run:

                      1. go run main.go

                        Your output will look similar to this:

                        Output
                        json map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:<nil> nullStringValue:<nil> objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!] date value: 2022-03-02T09:10:00Z

                        You can see the date value line showing the dateValue field extracted from the map and converted to a string value.

                        In this section, you updated your program to use the json.Unmarshal function with a map[string]interface{} variable to unmarshal JSON data into Go data. Then, you updated the program to extract the value of dateValue from the Go data and print it to the screen.

                        However, this update does show one of the downsides of using a map[string]interface{} to unmarshal JSON in Go. Since Go doesn’t know which type of data each field is (the only thing it knows is it’s an interface{}), the best it can do to unmarshal the data is make a best guess. This means complex values like time.Time for the dateValue field can’t be unmarshaled for you and can only be accessed as a string. A similar problem happens if you try to access any number value in a map this way. Since json.Unmarshal doesn’t know whether the number should be an int, a float, an int64, and so on, the best guess it can make is to put it into the most flexible number type available, a float64.

                        While using a map to decode JSON data can be flexible, it also leaves more work for you when interpreting the data you have. Similar to how the json.Marshal function can use struct values to generate JSON data, the json.Unmarshal function can use struct values to read JSON data. This can help remove the type assertion complexities of using a map by using the type definitions on the struct’s fields to determine which types the JSON data should be interpreted as. In the next section, you will update your program to use struct types to remove these complexities.

                        Parsing JSON Using a Struct

                          1. Conclusion

                            encoding/json package includes other useful functions and types that can be used for interacting with JSON. The json.MarshalIndent function, for example, can be used to pretty print JSON data for troubleshooting.

                            This tutorial is also part of the DigitalOcean How to Code in Go series. The series covers a number of Go topics, from installing Go for the first time to how to use the language itself.