12/08/2019, 09:03

Đọc và xử lý file YAML với Golang

Mở đầu YAML viết tắt của "YAML Ain't Markup Language" là định dạng phổ biến và thường được sử dụng rộng rãi để làm các file cấu hình mà ta thường bắt gặp ở các hệ thống như CI/CD, Ansible, Docker..v..v. Bởi tính trực quan, có cấu trúc và tương đối dễ đọc đối với người dùng, mà không ...

Mở đầu

YAML viết tắt của "YAML Ain't Markup Language" là định dạng phổ biến và thường được sử dụng rộng rãi để làm các file cấu hình mà ta thường bắt gặp ở các hệ thống như CI/CD, Ansible, Docker..v..v.

Bởi tính trực quan, có cấu trúc và tương đối dễ đọc đối với người dùng, mà không cần bỏ ra nhiều thời gian để học và làm quen. Nên định dạng yaml có phần dễ bắt gặp so với .cnf, .conf, .cfg, .cf hay .ini trong mảng dùng làm file cấu hình.

Vậy nên, trong quá trình làm việc chúng ta thường bắt gặp một số task phải xử lý file định dạng yaml này. Phần cũng mình thấy trên Viblo của chúng ta cũng chưa có bài nào viết về việc đọc và xử lý file yaml nhất là bằng ngôn ngữ khá mới như golang. Nên mình xin phép viết một bài về vấn đề này. Tuy cũng không quá khó hay có gì phức tạp cả, nhưng hy vọng có thể hữu ích cho những ai cần đến....

yaml.v2

Việc đọc và xử lý file yaml với golang thì có phần phức tạp hơn thông thường một chút. Nhất là nếu đen so với PHP, nhìn chung đọc file yaml với PHP có các function có trong extension yam, hay cực dễ dùng với package Symfony Yaml. Chỉ cần đưa đường dẫn file vào và ta nhận được một mảng, sau đó cứ thế xử lý tiếp với mảng này thôi.

Còn với golang thì rối hơn chút. Phần vì một bên là dynamic và một bên static language nên với go kiểu dữ liệu phải được xác định rõ trước. Nhất là với mỗi block trong file yml, nó có thể là một slice, một map hoặc một string nên việc parse một file yml thành ra tương đối khó. Thực tế thì việc đọc file yml không được go hỗ trợ một cách native. Tuy nhiên, hiện nay có một vài thư viện phần nào (chỉ phần nào thôi) cũng đã hỗ trợ chúng ta làm việc này. Nổi tiếng nhất là go-yaml có tới hơn 3000 lượt start trên github. (Có package hỗ trợ, nhưng không dễ xơi chút nào nhé)

Ta hãy làm một ví dụ nhỏ dưới đây, sử dụng package trên để parse file yml đơn giản thử xem. Đầu tiên là một cấu trúc đơn giản thôi nhé :

actions:
  - run
  - swim
  - kick

Đoạn này khá đơn giản, để lấy được các actions. Ta chỉ cần parse chúng vào một slice of string (có thể hiểu nôm na nó là một mảng các string ) như đoạn code mẫu sau:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	yaml "gopkg.in/yaml.v2"
)

type Yml struct {
	Actions []string `yaml:"actions"`
}

func main() {
	yml := Yml{}
	yamlFile, err := ioutil.ReadFile("example.yml")
	HandleError("Read yml file error -> ", err)

	e := yaml.Unmarshal(yamlFile, &yml)
	HandleError("Unmarshal error -> ", e)

	fmt.Println(yml.Actions)
}

func HandleError(message string, e error) {
	if e != nil {
		log.Fatal(message, e)
	}
}

Để ý rằng ta khai báo một struct Yml sau đó convert cả file yml vào struct này. Trong struct có trường Actions tường ứng với key của khối actions trong file. yaml:"actions" để thông báo rằng struct fied này tường ứng với khối có key là actions trong file yml.

Điểm thú vị là bạn không cần phải thêm yaml:"actions" nếu struc fied của chúng ta có tên đặt giống với key trong file yml. go-yaml sẽ tự động nhật biết key và parse vào struct file tương ứng. Thực thi chương trình trên ta được như sau:

[run swim kick]

Rồi, ví dụ trên khá đơn giản. Thực tế thì cũng chẳng có cái file yml nào lại đơn giản đến vậy cả, bây giờ ta thử với một mẫu yml có cấu trúc phức tạp hơn như sau:

actions:
  - run
  - swim
  - kick
  
person:
  john:
    name: John
    info: 
      address: HaNoi
      age: 26
      phone: 777
    skills:
      - php
      - golang
      - nodejs
  trump:
    name: Trump
    info: 
      address: SF
      age: 27
      phone: 888
    skills:
      - html
      - javascript
      - css

Khá khó, phải không nào. Bên trong khối person, john và trump là có kiểu map[string]??? với key có kiểu string thì chúng nhìn thôi ta biết rồi. Nhưng các khối bên trong thì lại có kiểu dữ liệu rất biến đổi khác nhau.

  • name:john có kiểu map[string]string
  • info lại có kiểu map[string]map[string]string
  • còn skills lại là slice of string

Trường hợp này làm mình rất bối rối, bởi không biết khai báo, định nghĩa struct Yml như đã nói ở trên như thế nào cho phù hợp. Tới đây, dẫn chúng ta đến khái niệm interface hay cụ thể hơn trong trường hợp của chúng ta là empty interface - interface{}

Nhắc lại khái niệm interface trong golang, thì interface có thể là một tập các method cũng đồng thời là một kiểu nên ta có thể khai báo một biến có kiểu interface. Hãy nhìn đoạn code mà mình lụm được trên mạng dưới đây:

package main

import "fmt"

type Dog struct {
    Age interface{}
}

func main() {
    dog := Dog{}
    dog.Age = "3"
    fmt.Printf("%#v %T
", dog.Age, dog.Age) // -> "3" string

    dog.Age = 3
    fmt.Printf("%#v %T
", dog.Age, dog.Age) // 3 int

    dog.Age = "not really an age"
    fmt.Printf("%#v %T", dog.Age, dog.Age) // "not really an age" string
}

Tại thời điểm runtime, go sẽ convert kiểu dữ liệu của biến được khai báo có type empty interface này, thành kiểu dữ liệu thực sự mà biến đang nắm dữ. (Cái này hại não quá, mình cũng không biết gải thích thế nào cho dễ hiểu hơn. Nhìn đoạn code trên có khi còn dễ hình dung hơn là mình giả thích nữa             </div>
            
            <div class=

0