Introduction
We have walked through some examples of working with JSON format in Golang. In today's article, I will guide you on removing fields from the struct or hiding them in JSON string. In the previous post, we already introduced the json package and UnMarshal()
function for parsing JSON string to a struct:
Package json implements encoding and decoding of JSON as defined in RFC 7159. The mapping between JSON and Go values is described in the documentation for the Marshal and Unmarshal functions.
func Unmarshal(data []byte, v any) error
: Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. If v is nil or not a pointer, Unmarshal returns an InvalidUnmarshalError.
Unmarshal uses the inverse of the encodings that Marshal uses, allocating maps, slices, and pointers as necessary, with the following additional rules:
To unmarshal JSON into a pointer, Unmarshal first handles the case of the JSON being the JSON literal null. In that case, Unmarshal sets the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into the value pointed at by the pointer. If the pointer is nil, Unmarshal allocates a new value for it to point to.
The encoding of each struct field can be customized by the format string stored under the "json" key in the struct field's tag. The format string gives the name of the field, possibly followed by a comma-separated list of options. The name may be empty in order to specify options without overriding the default field name.
Example 1: Parsing a JSON string to a struct
The code below demonstrates how to use the Unmarshal()
function to convert a JSON string to a struct:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string
Age int
}
func main() {
var user1 User
jsonStr := `{"Name":"Anna","Age":22}`
err := json.Unmarshal([]byte(jsonStr), &user1)
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("%+v", user1)
}
Example 2: Always skip parsing a field in a struct
Use json:"-"
tag to skip a field if what you want is to always skip it while encoding JSON string to a struct. Take note that this is not necessary if your field is not exported; the JSON encoder automatically ignores such fields. As discussed before, if the field starts with a lowercase letter, it will not be exported. Here is an example of using the json:"-"
tag to skip a field when parsing JSON string to a struct:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"-"`
Age int
}
func main() {
var user1 User
jsonStr := `{"Name":"Anna","Age":22}`
err := json.Unmarshal([]byte(jsonStr), &user1)
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("%+v", user1)
}
Output:
{Name: Age:22}
json:"-"
works both ways: parsing JSON string to a struct or a struct to a JSON string. The example below shows how it works when converting from a struct to a string. Even though the struct has the field, we won’t see it in the JSON output:
package main
import (
"encoding/json"
"fmt"
"os"
)
type PersonCus struct {
Name string
Age int `json:"-"`
}
func main() {
person1 := PersonCus{
Name: "Anna",
Age: 21,
}
data, err := json.Marshal(person1)
if err != nil {
fmt.Println("An error occured: %v", err)
os.Exit(1)
}
fmt.Println(string(data))
}
Output:
{"Name":"Anna"}
Example 3: Parsing JSON string to a map instead of a struct
We also can use a map[string]interface{}
 instead of a struct in this case. You can easily remove fields by calling the delete
built-in on the map for the fields to remove.
package main
import (
"encoding/json"
"fmt"
)
func main() {
var user1 map[string]interface{}
jsonStr := `{"Name":"Anna","Age":22}`
err := json.Unmarshal([]byte(jsonStr), &user1)
if err != nil {
fmt.Println("error:", err)
}
if _, ok := user1["Name"]; ok {
delete(user1, "Name")
}
fmt.Printf("%+v", user1)
}
Output:
map[Age:22]
Example 4: Dynamic parsing fields with the json omitempty tag
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.
Here is an example of how to use the omitempty
tag to dynamically parse a struct to a JSON string. Remember that we only want to ignore the field of the struct if it’s empty.
package main
import (
"encoding/json"
"fmt"
"os"
)
type PersonCus struct {
Name string `json:"name,omitempty"`
Age int `json:"age,omitempty"`
Pointer *string `json:"pointer,omitempty"`
}
func main() {
pointer := "pointer"
person1 := PersonCus{
Name: "Anna",
Age: 21,
Pointer: &pointer,
}
data, err := json.Marshal(person1)
if err != nil {
fmt.Println("An error occured: %v", err)
os.Exit(1)
}
fmt.Println(string(data))
// ALL of those are considered empty by Go
nilPerson := PersonCus{
Name: "",
Age: 0,
Pointer: nil,
}
dataNil, err := json.Marshal(nilPerson)
if err != nil {
fmt.Println("An error occured: %v", err)
os.Exit(1)
}
fmt.Println(string(dataNil))
}
Output:
{"name":"Anna","age":21,"pointer":"yes"}
{}
The second struct contains nothing at all. The omitempty
annotation causes all fields to be ignored since they are empty. It may seem contradictory at times, but zero-values and nil pointers are regarded as empty. If you actually want to use omitempty
, you need to deal with something like *int64 if you want to produce a 0. The rest is pretty much just common sense.
Summary
In this article, I have provided some solutions to remove or hide fields on both sides: from JSON string to a struct or from a struct to a JSON string. Sometimes we have to handle missing fields while unmarshalling some JSON into a struct and got confused for a while. You can use the few examples above as a reminder of how most of it works. Those are not fancy edge cases. Those are self-contained, completely functional code snippets that should be easy to follow.
References
https://pkg.go.dev/encoding/json#Marshal
Removing fields from struct or hiding them in JSON Response