Let's say I want to marshal a struct into YAML, and the struct already has all of its YAML tags defined, except there is one tag that I want to change. How can I change the behavior for this single field without changing the struct itself? Assume the struct is coming from a third-party package.
Here's an example to demonstrate, along with my best attempt. Let's assume that the User
struct (and its associated Secret
struct) are coming from a third-party package, so we can't modify them.
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
type User struct {
Email string `yaml:"email"`
Password *Secret `yaml:"password"`
}
type Secret struct {
s string
}
// MarshalYAML implements the yaml.Marshaler interface for Secret.
func (sec *Secret) MarshalYAML() (interface{}, error) {
if sec != nil {
// Replace `"<secret>"` with `sec.s`, and it gets the desired
// behavior. But I can't change the Secret struct:
return "<secret>", nil
}
return nil, nil
}
func (sec *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error {
var st string
if err := unmarshal(&st); err != nil {
return err
}
sec.s = st
return nil
}
// My best attempt at the solution:
type SolutionAttempt struct {
User
}
func (sol *SolutionAttempt) MarshalYAML() (interface{}, error) {
res, err := yaml.Marshal(
struct {
// I don't like having to repeat all these fields from User:
Email string `yaml:"email"`
Password string `yaml:"password"`
}{
Email: sol.User.Email,
Password: sol.User.Password.s,
},
)
if err != nil {
return nil, err
}
return string(res), nil
}
func main() {
user := &User{}
var data = `
email: [email protected]
password: asdf
`
err := yaml.Unmarshal([]byte(data), user)
if err != nil {
fmt.Printf("errors! %s", err)
return
}
buf, err := yaml.Marshal(user)
if err != nil {
fmt.Printf("errors! %s", err)
return
}
// Without touching User or Secret, how can I unmarshall an
// instance of User that renders the secret?
fmt.Printf("marshalled output:\n%s\n", buf)
///////////////////////////////////////////////////////
// attempted solution:
///////////////////////////////////////////////////////
sol := &SolutionAttempt{}
var data2 = `
user:
email: [email protected]
password: asdf
`
err = yaml.Unmarshal([]byte(data2), sol)
if err != nil {
fmt.Printf("errors! %s", err)
return
}
buf, err = yaml.Marshal(sol)
if err != nil {
fmt.Printf("errors! %s", err)
return
}
fmt.Printf("attempted solution marshalled output:\n%s\n", buf)
}
Here is a Go playground link of the code above: https://go.dev/play/p/ojiPv4ylCEq
How can I change the behavior for this single field without changing the struct itself? Assume the struct is coming from a third party package.
This simply is impossible.
Your "best attempt" is the way to Go.