Custom marshal/unmarshal
Below is a basic implementation of a YAML marshaller and unmarshaller in Go, supporting struct tags for field mapping. It mimics the functionality of external libraries by manually handling struct reflection and basic YAML parsing.
Code Example
package main
import (
"errors"
"fmt"
"reflect"
"strings"
)
// Marshal converts a struct into a YAML string
func Marshal(v interface{}) (string, error) {
value := reflect.ValueOf(v)
if value.Kind() != reflect.Struct {
return "", errors.New("marshal: input must be a struct")
}
var builder strings.Builder
marshalStruct(value, &builder, 0)
return builder.String(), nil
}
// Helper to recursively marshal a struct
func marshalStruct(value reflect.Value, builder *strings.Builder, indentLevel int) {
indent := strings.Repeat(" ", indentLevel)
typ := value.Type()
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
fieldType := typ.Field(i)
// Get the YAML tag or default to the field name
tag := fieldType.Tag.Get("yaml")
if tag == "" {
tag = fieldType.Name
}
// Handle struct fields
if field.Kind() == reflect.Struct {
builder.WriteString(fmt.Sprintf("%s%s:\n", indent, tag))
marshalStruct(field, builder, indentLevel+1)
} else {
builder.WriteString(fmt.Sprintf("%s%s: %v\n", indent, tag, field.Interface()))
}
}
}
// Unmarshal parses a YAML string into a struct
func Unmarshal(data string, v interface{}) error {
value := reflect.ValueOf(v)
if value.Kind() != reflect.Ptr || value.Elem().Kind() != reflect.Struct {
return errors.New("unmarshal: input must be a pointer to a struct")
}
lines := strings.Split(data, "\n")
parseYAML(lines, value.Elem(), 0, "")
return nil
}
// Helper to recursively parse YAML
func parseYAML(lines []string, value reflect.Value, indentLevel int, parentKey string) {
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
continue
}
parts := strings.SplitN(trimmed, ":", 2)
if len(parts) < 2 {
continue
}
key := strings.TrimSpace(parts[0])
val := strings.TrimSpace(parts[1])
// Map to struct fields
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
fieldType := value.Type().Field(i)
tag := fieldType.Tag.Get("yaml")
if tag == "" {
tag = fieldType.Name
}
if key == tag {
if field.Kind() == reflect.String {
field.SetString(val)
} else if field.Kind() == reflect.Int {
// Note: Add error handling for conversion in a real implementation
field.SetInt(int64(len(val)))
} else if field.Kind() == reflect.Struct {
// Recursively parse structs
// Note: Add implementation to parse nested maps
}
break
}
}
}
}
// Example struct with YAML tags
type Person struct {
Name string `yaml:"name"`
Age int `yaml:"age"`
Email string `yaml:"email"`
Address struct {
Street string `yaml:"street"`
City string `yaml:"city"`
} `yaml:"address"`
}
func main() {
// Example data
person := Person{
Name: "John Doe",
Age: 30,
Email: "john.doe@example.com",
Address: struct {
Street string `yaml:"street"`
City string `yaml:"city"`
}{
Street: "123 Elm St",
City: "Springfield",
},
}
// Marshal struct to YAML
yamlData, err := Marshal(person)
if err != nil {
fmt.Println("Marshal error:", err)
return
}
fmt.Println("Marshalled YAML:")
fmt.Println(yamlData)
// Unmarshal YAML to struct
inputYAML := `
name: Jane Doe
age: 25
email: jane.doe@example.com
address:
street: 456 Oak St
city: Shelbyville
`
var newPerson Person
err = Unmarshal(inputYAML, &newPerson)
if err != nil {
fmt.Println("Unmarshal error:", err)
return
}
fmt.Println("\nUnmarshalled Struct:")
fmt.Printf("%+v\n", newPerson)
}
Features:
Struct Tags: Supports yaml
tags for field mapping.
Nested Structs: Handles nested structs for marshalling and basic support for unmarshalling.
Manual Reflection: Uses Go's reflection package to dynamically process structs.
Limitations:
Data Types: Currently handles only basic types like strings and integers. You can expand it to handle floats, booleans, slices, and maps.
Nested Parsing: For unmarshalling, support for nested structs is limited and needs further enhancement.
Error Handling: Add comprehensive error checks for parsing and type conversion.
This implementation provides a starting point for a simple YAML marshaller and unmarshaller without relying on external libraries.
Last modified: 19 December 2024