Golang How to Marshal and Unmarshal json

eye-catchGolang
Sponsored links

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.

Sponsored links

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.

Sponsored links

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

Copied title and URL