Golang Parse YAML file [With or Without Struct]


GO

Author: Tuan Nguyen
Reviewer: Deepak Prasad

In this tutorial, we will walk through how to work with yaml file in Golang. We will cover the following basic areas of YAML parsing:

  1. Parse YAML from a variable or file into struct
  2. Parse YAML from a variable or file into map (without using struct)
  3. Access individual nested elements from YAML file as part of map or structs

 

Golang parse YAML into struct

In golang we can use the gopkg.in/yaml.v3 package to parse YAML data into a struct. We will need to define a struct that matches the structure of the YAML data. Then we can use the yaml.Unmarshal function to parse the YAML data into an instance of that struct.

The import path for the package is gopkg.in/yaml.v3.

To install it, run:

$ go get gopkg.in/yaml.v3
go: downloading gopkg.in/yaml.v3 v3.0.1
go get: added gopkg.in/yaml.v3 v3.0.1

 

Example-1: Parse a simple YAML File into struct

Here is a simple YAML file that has two fields: name and age, both are strings which we will parse into a struct:

name: Amit Kumar
age: 35

Here is a sample code to parse the YAML file and return the content into pre-defined struct:

package main

import (
	"fmt"
	"io/ioutil"

	"gopkg.in/yaml.v3"
)

type Config struct {
	Name string `yaml:"name"`
	Age  int    `yaml:"age"`
}

func main() {
	// Read the file
	data, err := ioutil.ReadFile("config.yaml")
	if err != nil {
		fmt.Println(err)
		return
	}

	// Create a struct to hold the YAML data
	var config Config

	// Unmarshal the YAML data into the struct
	err = yaml.Unmarshal(data, &config)
	if err != nil {
		fmt.Println(err)
		return
	}

	// Print the data
	fmt.Println(config)
}

In this example, the struct Config is defined with fields that match the structure of the YAML data. The yaml struct tags are used to indicate the corresponding field names in YAML.

Then, the ioutil.ReadFile function is used to read the contents of the file "config.yaml" into the data variable. Then the yaml.Unmarshal function is used to parse the YAML data into an instance of struct Config.

Keep in mind that, if the structure of the YAML is not known and we want to handle all types of YAML then it is recommended to use map[interface{}]interface{} instead of struct which we will cover in next examples.

Output:

# go run main.go 
{Amit Kumar 35}

 

Example-2: Parse nested YAML file into struct

In this example we will parse a more complex nested YAML structure. Here is our sample YAML file config.yaml which we will use in our example code:

name: Amit Kumar
age: 35
address:
  street: 123 Some Random Street
  city: Chennai
  state: TN
  zip: 763098
phoneNumbers:
- type: home
  number: 0123456789
- type: work
  number: 0987654321

The below example golang code defines a struct Person with fields that match the structure of the YAML data. The yaml struct tags are used to indicate the corresponding field names in YAML.

In this example, the Person struct contains nested structs: Address and PhoneNumber and also a slice of PhoneNumber structs, which is used to handle the array of phone numbers in the YAML file.

package main

import (
	"fmt"
	"io/ioutil"

	"gopkg.in/yaml.v3"
)

type Address struct {
	Street string `yaml:"street"`
	City   string `yaml:"city"`
	State  string `yaml:"state"`
	Zip    string `yaml:"zip"`
}

type PhoneNumber struct {
	Type   string `yaml:"type"`
	Number string `yaml:"number"`
}

type Person struct {
	Name        string        `yaml:"name"`
	Age         int           `yaml:"age"`
	Address     Address       `yaml:"address"`
	PhoneNumber []PhoneNumber `yaml:"phoneNumbers"`
}

func main() {
	// Read the file
	data, err := ioutil.ReadFile("config.yaml")
	if err != nil {
		fmt.Println(err)
		return
	}

	// Create a struct to hold the YAML data
	var person Person

	// Unmarshal the YAML data into the struct
	err = yaml.Unmarshal(data, &person)
	if err != nil {
		fmt.Println(err)
		return
	}

	// Print the data
	fmt.Println(person)
}

In the main function, we use the ioutil.ReadFile function to read the contents of the file "config.yaml" into the data variable. Then the yaml.Unmarshal function is used to parse the YAML data into an instance of struct Person.

Finally, we print the struct which contains the parsed data from the yaml file.

Output:

# go run main.go 
{Amit Kumar 35 {123 Some Random Street Chennai TN 763098} [{home 0123456789} {work 0987654321}]}

 

Example-3: Access individual fields of YAML File using Structs

Once we have parsed a YAML file into a struct then we can access individual fields or elements of the data by referencing them by their keys as shown below.

	fmt.Println(person.Name) // Amit Kumar

In case of nested fields in struct we can access them using the dot notation. Here's an example of how we can access a nested field in a struct:

	fmt.Println(person.Address.City) // Chennai

The phone numbers in our YAML file are defined as slice of PhoneNumber structs, which is the field PhoneNumber in the Person struct.

	for _, phone := range person.PhoneNumber {
		if phone.Type == "home" {
			fmt.Println(phone.Number) // 0123456789
		}
	}

Here the range keyword is used to iterate over the slice and it returns two values: an index and a copy of the element at that index. In this case, we are not using the index, so we use the blank identifier _ to ignore it. For each element in the slice, it checks the value of the Type field using an if statement. If the value is "home", it prints the value of the Number field using fmt.Println

Alternatively we can use a for loop with index and check the Type field of the element in the PhoneNumber slice.

	for i := 0; i < len(person.PhoneNumber); i++ {
		if person.PhoneNumber[i].Type == "work" {
			fmt.Println(person.PhoneNumber[i].Number) // 0987654321
		}
	}

Here we use a for loop with index to iterate over the slice of PhoneNumber structs. The for loop starts with an initializer i:=0, a condition i < len(person.PhoneNumber) and an increment i++. Inside the loop, it checks the Type field of the element in the PhoneNumber slice using an if statement. If the value is "home", it prints the value of the Number field using fmt.Println

 

Golang parse YAML into Map (Without using struct)

Example-1: Parse YAML variable into map

In this example we parse YAML stored in a variable into map.

package main

import (
	"fmt"

	"gopkg.in/yaml.v3"
)

func main() {
	// YAML string stored in a variable
	yamlString := `
name: Amit Kumar
age: 34
address:
  city: Chennai
  state: TN
`
	// Map to store the parsed YAML data
	var data map[string]interface{}

	// Unmarshal the YAML string into the data map
	err := yaml.Unmarshal([]byte(yamlString), &data)
	if err != nil {
		fmt.Println(err)
	}

	// Access data in the map
	fmt.Println("Name:", data["name"])
	fmt.Println("Age:", data["age"])
	fmt.Println("City:", data["address"].(map[string]interface{})["city"])
}

In this example, we first define a variable yamlString which contains a YAML string. Then we define an empty map data that will store the parsed YAML data.

We then use the yaml.Unmarshal function to parse the YAML string stored in the variable yamlString, and store the result in the data map. The Unmarshal function takes two arguments: the YAML data and a pointer to the variable that will store the result.

# go run main.go 
Name: Amit Kumar
Age: 34
City: Chennai

 

Example-2: Parse YAML file into map

In this example, we will parse a more complex yaml (config.yaml) file. In this example, we define a complex struct instead of simple map to store the parsed data.

name: Amit Kumar
age: 35
address:
  street: 123 Some Random Street
  city: Chennai
  state: TN
  zip: 763098
phoneNumbers:
- type: home
  number: 0123456789
- type: work
  number: 0987654321

In this example we again use the gopkg.in/yaml.v3 package to parse YAML data into a map. We will use the yaml.Unmarshal function to parse the YAML data into a map[string]interface{} which can hold any YAML data.

package main

import (
	"fmt"
	"io/ioutil"

	"gopkg.in/yaml.v3"
)

func main() {
	// Read the file
	data, err := ioutil.ReadFile("config.yaml")
	if err != nil {
		fmt.Println(err)
		return
	}

	// Create a map to hold the YAML data
	var config map[string]interface{}

	// Unmarshal the YAML data into the map
	err = yaml.Unmarshal(data, &config)
	if err != nil {
		fmt.Println(err)
		return
	}

	// Print the data
	fmt.Println(config)
}

In this example, the config variable is defined as map[string]interface{}. This is because the yaml.Unmarshal function returns a map with keys of type string and values of type interface{}, which can hold any YAML value.

Then, the ioutil.ReadFile function is used to read the contents of the file "config.yaml" into the data variable. Then the yaml.Unmarshal function is used to parse the YAML data into the map config.

Output:

# go run main.go 
map[address:map[city:Chennai state:TN street:123 Some Random Street zip:763098] age:35 name:Amit Kumar phoneNumbers:[map[number:1.23456789e+08 type:home] map[number:9.87654321e+08 type:work]]]

 

Example-3:  Access individual nested fields of YAML File using map

Similar to our previous example where we accessed the data of YAML file using struct, similarly we can access individual fields or elements of the data by referencing them by their keys in the map.

	fmt.Println(config["name"]) // Amit Kumar

To access the nested field, we need to use a type assertion to convert the value to the appropriate type. The type assertion should be used to assert that the nested field is of a certain type, in this case a map[string]interface{}.

Here is an example of how we can access the "city" field in the nested "address" field:

	address := config["address"].(map[string]interface{})
	fmt.Println(address["city"]) // Chennai

To access the phone numbers in our example is more tricky as it involves some more operation. Here the phone numbers are stored in a slice so we need to iterate over the slice to get individual value. Inside the for loop, for each phone number, we use another type assertion to convert the phone number to a map of type map[string]interface{}. Then it checks the value of type and if the value of the "type" field is "home", it prints the value of the "number" field using fmt.Println, but the value is of type interface{}, it is printed as a byte slice.

Then we use strconv.FormatFloat function, to convert the float64 to a string.

	phoneNumbers := config["phoneNumbers"].([]interface{})
	for _, phoneMap := range phoneNumbers {
		phone := phoneMap.(map[string]interface{})
		if phone["type"] == "home" {
			fmt.Println(phone["number"]) // will print in byte format

			// convert float64 to string
			number := phone["number"].(float64)
			numberString := strconv.FormatFloat(number, 'f', -1, 64)
			fmt.Println(numberString) // 123456789

		}
	}

 

Summary

In this tutorial we learned how to parse YAML file (Simple and Complex with multiple nested keys) using golang yaml package. We use yaml.Unmarshal to store the values of YAML key into struct or map and then access the individual elements.

 

References

https://pkg.go.dev/gopkg.in/yaml.v3
go - How to read a YAML file - Stack Overflow

 

Tuan Nguyen

Tuan Nguyen

He is proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment