When you start working with Golang, you might see a struct definition like the following.
type employee struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
}
It absolutely looks like JSON-related things but how it actually works?
If you need to parse or convert JSON because your application communicates with REST API or something similar, this post is for you.
How to convert from struct to json and vice versa
Golang offers a simple way to convert. Let’s define the following simple struct.
type product struct {
Name string
Price int
}
struct to json
json.Marshal(json)
can be the first option.
item := product{
Name: "Apple",
Price: 55,
}
jsonBytes, err := json.Marshal(item)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(jsonBytes))
// {"Name":"Apple","Price":55}
The json.Marshal
returns byte array. So it’s necessary to convert it to string.
json to struct with indent
Use json.MarshalIndent()
instead if you want to make it more readable.
prefix := ""
indent := "\t"
jsonBytes2, err := json.MarshalIndent(item, prefix, indent)
fmt.Println(string(jsonBytes2))
// {
// "Name": "Apple",
// "Price": 55
// }
This is useful when you want to write the content of the struct to log.
json to struct
Use json.Unmarshal(jsonBytes, *dest)
to convert JSON to struct.
var fromJson []product
myJson := []byte(`[
{"Name": "Apple", "prICe": 11, "id": 3},
{"Name": "Melon", "PricE": 22, "id": 4}
]`)
err = json.Unmarshal(myJson, &fromJson)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%+v\n", fromJson)
// [{Name:Apple Price:11} {Name:Melon Price:22}]
The second parameter needs to be a pointer.
As you can see, the undefined key value is not assigned to the struct. However, it is not case-sensitive. Even though one of the key letters’ cases is different, the value is correctly assigned.
Define the name usesd in the json
If you want to define the name used in JSON, you can define it on the same line as the variable in a struct. The format is the following.
json:"key_name"
Let’s see this example.
type employee struct {
Name string "json:name"
Age int `json:"age"`
Gender string `json:"gender"`
Job string `json:"role"`
WithoutSchema string
}
employee := employee{
Name: "Yuto",
Age: 35,
Gender: "Male",
Job: "Software Developer",
}
prefix := ""
indent := "\t"
jsonBytes2, _ := json.MarshalIndent(employee, prefix, indent)
fmt.Println(string(jsonBytes2))
// {
// "Name": "Yuto",
// "age": 35,
// "gender": "Male",
// "role": "Software Developer",
// "WithoutSchema": ""
// }
The Name
variable doesn’t have double quotes for the key itself. Therefore, the variable name is used in the JSON as it is.
Age
and Gender
have the expected format, so the first letter is lowercase. The name of Job in JSON is totally different but it’s also written as expected.
As you saw in the previous section, the variable name is used since it has no schema definition for WithoutSchema
.
Is a key-value assigned to struct when the json name is different?
What happens if the two struct has the same variable name but a different JSON name? Let’s define the following struct. Name has all the lower cases but the same letter. Gender has totally different naming.
type person struct {
Name string `json:"name"`
Age int `json:"age"`
Gender string `json:"fooo"`
}
Then, let’s try to convert a JSON to struct.
employee := employee{
Name: "Yuto",
Age: 35,
Gender: "Male",
Job: "Software Developer",
}
jsonBytes, _ := json.Marshal(employee)
var yuto person
err := json.Unmarshal(jsonBytes, &yuto)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%+v\n", yuto)
// {Name:Yuto Age:35 Gender:}
fmt.Printf("Name: %s, Age: %d, Gender: %s\n", yuto.Name, yuto.Age, yuto.Gender)
// Name: Yuto, Age: 35, Gender:
The destination name is fooo
while the JSON object has gender hence the key value is not assigned to the Gender
property.
How to assign null to json
If the variable is defined as a non-pointer type, the default value is assigned when no data is specified.
If you want to assign null to the key, you need to define the variable as a pointer.
type jsonTestStruct struct {
// add asterisk to the data type
StringPointer0 *string `json:"stringPointer0"`
StringPointer1 *string `json:"stringPointer1"`
}
str := ""
obj := jsonTestStruct{
StringPointer0: &str,
StringPointer1: nil,
}
jsonBytes, _ := json.MarshalIndent(obj, prefix, indent)
fmt.Println(string(jsonBytes))
// {
// "stringPointer0": "",
// "stringPointer1": null,
// }
You don’t have to explicitly set nil
.
How to omit the key when the value is not set
There are some cases where the key should not appear in the JSON if the value is not specified. In this case, you can add omitempty
keyword in the schema.
type jsonTestStruct struct {
StringPointer0 *string `json:"stringPointer0"`
StringPointer1 *string `json:"stringPointer1"`
StringPointer2 *string `json:"stringPointer2,omitempty"`
StringValue string `json:"stringValue,omitempty"`
}
It must be in the double quotes. Don’t add a space after the comma.
str := ""
obj := jsonTestStruct{
StringPointer2: &str,
StringValue: "",
}
jsonBytes, _ := json.MarshalIndent(obj, prefix, indent)
fmt.Println(string(jsonBytes))
// {
// "stringPointer0": null,
// "stringPointer1": null,
// "stringPointer2": "",
// }
stringPointer2
has an empty string there because it is a pointer type. On the other hand, StringValue is omitted. According to the official documentation, if the value is the default value, the key is omitted.
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.
https://pkg.go.dev/encoding/json#Unmarshal
If you want to differentiate between a default value and null, you need to define the variable as a pointer type.
Let’s test a little bit more for those types described above.
type jsonTestDefault struct {
IntValue int `json:"intValue,omitempty"`
IntArray []int `json:"intArray,omitempty"`
BoolValue bool `json:"boolValue,omitempty"`
MapValue map[int]string `json:"mapValue,omitempty"`
}
obj := jsonTestDefault{
IntValue: 0,
IntArray: []int{},
BoolValue: false,
MapValue: map[int]string{},
}
jsonBytes, _ := json.MarshalIndent(obj, prefix, indent)
fmt.Println(string(jsonBytes))
// {}
The result is as expected.
obj := jsonTestDefault{
IntValue: -1,
IntArray: []int{0},
BoolValue: true,
MapValue: map[int]string{0: ""},
}
jsonBytes, _ := json.MarshalIndent(obj, prefix, indent)
fmt.Println(string(jsonBytes))
// {
// "intValue": -1,
// "intArray": [
// 0
// ],
// "boolValue": true,
// "mapValue": {
// "0": ""
// }
// }
When the value is set, all of them are set correctly.
How to ignore key
If there are some credentials in the struct, you might not want to add them to the JSON. In this case, define the name as dash “-“.
type jsonTestOther struct {
AsIs int `json:""`
Ignored string `json:"-"`
Dash string `json:"-,"`
}
obj := jsonTestOther{
AsIs: 11,
Ignored: "this is ignored",
Dash: "this is not ignored",
}
jsonBytes, _ := json.MarshalIndent(obj, prefix, indent)
fmt.Println(string(jsonBytes))
// {
// "AsIs": 11,
// "-": "this is not ignored"
// }
Note that the key is not ignored if you add a comma followed by a dash.
How to create nested json structure
You might already have the idea. You need to just define a struct where one of the keys has another struct.
type department struct {
Name string `json:"name"`
Members []employee `json:"members,omitempty`
}
type employee struct {
Name string "json:name"
Age int `json:"age"`
Gender string `json:"gender"`
Job string `json:"role"`
WithoutSchema string
}
Then, the rest is the same.
employees := []employee{
{
Name: "Yuto",
Age: 35,
Gender: "Male",
Job: "Software Developer",
},
{
Name: "Daniel",
Age: 40,
Gender: "Male",
Job: "Software Tester",
},
}
department := department{
Name: "Technical Feeder",
Members: employees,
}
jsonBytes, _ := json.MarshalIndent(department, prefix, indent)
fmt.Println(string(jsonBytes))
// {
// "name": "Technical Feeder",
// "Members": [
// {
// "Name": "Yuto",
// "age": 35,
// "gender": "Male",
// "role": "Software Developer",
// "WithoutSchema": ""
// },
// {
// "Name": "Daniel",
// "age": 40,
// "gender": "Male",
// "role": "Software Tester",
// "WithoutSchema": ""
// }
// ]
// }
Comments