Go yaml
ライブラリ
go-yaml/yaml - Go言語のデファクトスタンダードYAMLライブラリ
概要
go-yaml/yamlは、Go言語で最も幅広く使われているYAMLパーサーです。YAML 1.1とYAML 1.2仕様をサポートし、設定ファイル、データシリアライゼーション、CI/CDパイプラインなど様々な用途で活用されています。構造体タグによる柔軟なマッピング、アンカーとエイリアスの完全サポート、マルチドキュメント処理など、YAML仕様の幅広い機能を実装しています。
詳細
go-yaml/yamlは、gopkg.in/yaml.v3として提供される成熟したYAMLライブラリで、encoding/jsonパッケージと似たAPIを提供します。バージョン3では、ノードベースのAPIが追加され、YAMLドキュメントのプログラマティックな操作が可能になりました。
ライブラリは、YAMLの複雑な機能を網羅的にサポートしています。アンカー(&)とエイリアス(*)による参照機能、複数行文字列の様々な表現方法(|、>、|−、>-など)、カスタムタグによる型安全なデシリアライザーの実装など、高度な機能が含まれています。
特に注目すべき機能として、yaml.Nodeを使用したAST(抽象構文木)操作があります。これにより、YAMLドキュメントの構造を保持したまま変更を加えたり、コメントやフォーマットを保持したラウンドトリップ処理が可能です。
メリット・デメリット
メリット
- 完全なYAMLサポート: YAML 1.1/1.2仕様の包括的な実装
- 成熟度: Goコミュニティで最も信頼されているYAMLライブラリ
- 柔軟なAPI: encoding/jsonと似た直感的なAPI設計
- ノードAPI: ASTレベルでのYAML操作が可能
- アンカー/エイリアス: 複雑なYAML構造の完全サポート
- カスタムマーシャラー: 独自のシリアライゼーションロジック実装可能
デメリット
- パフォーマンス: JSONよりパースが遅い
- メモリ使用量: 大きなYAMLファイルでメモリ消費が大きい
- エラーメッセージ: パースエラーが分かりにくい場合がある
- 脆弱性リスク: アンカーを使った10億笑い攻撃の可能性
- 型変換の曖昧さ: YAMLの柔軟な型システムによる予期しない変換
- 標準ライブラリではない: 外部依存として管理が必要
参考ページ
書き方の例
1. 基本的なYAMLエンコード・デコード
package main
import (
"fmt"
"log"
"gopkg.in/yaml.v3"
)
type Config struct {
Server struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
} `yaml:"server"`
Database struct {
Driver string `yaml:"driver"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"`
DBName string `yaml:"dbname"`
} `yaml:"database"`
Features []string `yaml:"features"`
Settings map[string]string `yaml:"settings"`
}
func main() {
// 構造体からYAMLへ
config := Config{}
config.Server.Host = "localhost"
config.Server.Port = 8080
config.Database.Driver = "postgres"
config.Database.Host = "db.example.com"
config.Database.Port = 5432
config.Database.Username = "appuser"
config.Database.Password = "secret"
config.Database.DBName = "myapp"
config.Features = []string{"auth", "api", "cache"}
config.Settings = map[string]string{
"timeout": "30s",
"retry": "3",
}
yamlData, err := yaml.Marshal(&config)
if err != nil {
log.Fatal(err)
}
fmt.Println("YAML出力:")
fmt.Println(string(yamlData))
// YAMLから構造体へ
var decoded Config
err = yaml.Unmarshal(yamlData, &decoded)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nデコード結果: %+v\n", decoded)
}
2. アンカーとエイリアスの使用
package main
import (
"fmt"
"gopkg.in/yaml.v3"
)
func anchorExample() {
yamlData := `
defaults: &defaults
adapter: postgres
host: localhost
port: 5432
development:
<<: *defaults
database: myapp_development
test:
<<: *defaults
database: myapp_test
production:
<<: *defaults
host: db.production.com
database: myapp_production
pool: 10
`
var result map[string]interface{}
err := yaml.Unmarshal([]byte(yamlData), &result)
if err != nil {
panic(err)
}
// 結果を再びYAMLに変換して表示
output, _ := yaml.Marshal(result)
fmt.Println("アンカー展開後:")
fmt.Println(string(output))
}
3. カスタムマーシャラーの実装
package main
import (
"fmt"
"gopkg.in/yaml.v3"
"time"
"strings"
)
type Duration struct {
time.Duration
}
// YAMLマーシャラーの実装
func (d Duration) MarshalYAML() (interface{}, error) {
return d.Duration.String(), nil
}
// YAMLアンマーシャラーの実装
func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return err
}
duration, err := time.ParseDuration(s)
if err != nil {
return err
}
d.Duration = duration
return nil
}
type ServerConfig struct {
Timeout Duration `yaml:"timeout"`
IdleTimeout Duration `yaml:"idle_timeout"`
MaxLifetime Duration `yaml:"max_lifetime"`
}
func customMarshalerExample() {
config := ServerConfig{
Timeout: Duration{30 * time.Second},
IdleTimeout: Duration{5 * time.Minute},
MaxLifetime: Duration{24 * time.Hour},
}
yamlData, _ := yaml.Marshal(&config)
fmt.Println("YAML出力:")
fmt.Println(string(yamlData))
// デコードテスト
yamlInput := `
timeout: 45s
idle_timeout: 10m
max_lifetime: 48h
`
var decoded ServerConfig
yaml.Unmarshal([]byte(yamlInput), &decoded)
fmt.Printf("デコード結果:\n")
fmt.Printf("Timeout: %v\n", decoded.Timeout.Duration)
fmt.Printf("IdleTimeout: %v\n", decoded.IdleTimeout.Duration)
fmt.Printf("MaxLifetime: %v\n", decoded.MaxLifetime.Duration)
}
4. ノードAPIを使ったYAML操作
package main
import (
"fmt"
"gopkg.in/yaml.v3"
"bytes"
)
func nodeAPIExample() {
yamlData := `
app:
name: MyApp
version: 1.0.0
# これはコメントです
debug: true
servers:
- name: web-1
host: 192.168.1.10
- name: web-2
host: 192.168.1.11
`
var root yaml.Node
err := yaml.Unmarshal([]byte(yamlData), &root)
if err != nil {
panic(err)
}
// ノードを走査して変更
modifyNode(&root)
// 変更後のYAMLを出力
var buf bytes.Buffer
encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2)
encoder.Encode(&root)
fmt.Println("変更後のYAML:")
fmt.Println(buf.String())
}
func modifyNode(node *yaml.Node) {
if node.Kind == yaml.DocumentNode {
for _, child := range node.Content {
modifyNode(child)
}
} else if node.Kind == yaml.MappingNode {
for i := 0; i < len(node.Content); i += 2 {
key := node.Content[i]
value := node.Content[i+1]
// versionフィールドを更新
if key.Value == "version" && value.Kind == yaml.ScalarNode {
value.Value = "2.0.0"
value.LineComment = "# Updated version"
}
modifyNode(value)
}
} else if node.Kind == yaml.SequenceNode {
for _, child := range node.Content {
modifyNode(child)
}
}
}
5. ストリーミング処理(複数ドキュメント)
package main
import (
"fmt"
"gopkg.in/yaml.v3"
"io"
"strings"
)
type Document struct {
Kind string `yaml:"kind"`
APIVersion string `yaml:"apiVersion"`
Metadata map[string]string `yaml:"metadata"`
Spec interface{} `yaml:"spec"`
}
func streamingExample() {
// 複数のYAMLドキュメントを含むデータ
yamlData := `
---
kind: Service
apiVersion: v1
metadata:
name: web-service
namespace: default
spec:
ports:
- port: 80
targetPort: 8080
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: web-deployment
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: web
---
kind: ConfigMap
apiVersion: v1
metadata:
name: web-config
namespace: default
data:
config.yaml: |
server:
port: 8080
`
decoder := yaml.NewDecoder(strings.NewReader(yamlData))
var docs []Document
for {
var doc Document
err := decoder.Decode(&doc)
if err == io.EOF {
break
}
if err != nil {
panic(err)
}
docs = append(docs, doc)
fmt.Printf("ドキュメント: %s/%s - %s\n",
doc.APIVersion, doc.Kind, doc.Metadata["name"])
}
fmt.Printf("\n合計 %d 個のドキュメントを読み込みました\n", len(docs))
}
6. エラーハンドリングとバリデーション
package main
import (
"fmt"
"gopkg.in/yaml.v3"
"errors"
)
type AppConfig struct {
Name string `yaml:"name" validate:"required"`
Version string `yaml:"version" validate:"required,semver"`
Port int `yaml:"port" validate:"required,min=1,max=65535"`
Debug bool `yaml:"debug"`
}
// UnmarshalYAML カスタムバリデーション付き
func (c *AppConfig) UnmarshalYAML(value *yaml.Node) error {
type plain AppConfig
if err := value.Decode((*plain)(c)); err != nil {
return err
}
// バリデーション
if c.Name == "" {
return errors.New("name is required")
}
if c.Port < 1 || c.Port > 65535 {
return errors.New("port must be between 1 and 65535")
}
return nil
}
func errorHandlingExample() {
// 正常なケース
validYAML := `
name: MyApplication
version: 1.2.3
port: 8080
debug: false
`
var config AppConfig
err := yaml.Unmarshal([]byte(validYAML), &config)
if err != nil {
fmt.Printf("エラー: %v\n", err)
} else {
fmt.Printf("正常に読み込み: %+v\n", config)
}
// エラーケース
invalidYAML := `
name: ""
version: 1.2.3
port: 70000
debug: false
`
var invalidConfig AppConfig
err = yaml.Unmarshal([]byte(invalidYAML), &invalidConfig)
if err != nil {
fmt.Printf("\nバリデーションエラー: %v\n", err)
}
// YAML構文エラーの例
syntaxErrorYAML := `
name: MyApp
version: 1.2.3
port: 8080 # インデントエラー
`
err = yaml.Unmarshal([]byte(syntaxErrorYAML), &config)
if err != nil {
fmt.Printf("\n構文エラー: %v\n", err)
}
}