xaxes
xaxes

Reputation: 397

Unmarshal a YAML to a struct with unexpected fields in Go

I encountered an issue while trying to unmarshal a struct with an unexported field using github.com/go-yaml/yaml. The struct looks like:

type Example struct {
    ExportedField   string                     `yaml:"exported-field"`
    OneMoreExported string                     `yaml:"one-more-exported"`
    unexportedField map[string]*AnotherExample `yaml:"unexported-field"`
}

type AnotherExample struct {
    Name string `yaml:"name"`
}

And I'd like to unmarshal such YAML as

exported-field: lorem ipsum
one-more-exported: dolor set
unexported-field:
  something:
    name: anything

What I tried is a custom unmarshaler:

func (example *Example) UnmarshalYAML(unmarshal func(interface{}) error) error {
    type Alias Example
    tmp := struct {
        UnexportedField map[string]*AnotherExample `yaml:"unexported-field"`
        *Alias
    }{
        Alias: (*Alias)(example),
    }
    if err := unmarshal(&tmp); err != nil {
        return err
    }
    if tmp.UnexportedField != nil {
        example.unexportedField = tmp.UnexportedField
    }
    example.CopyJobNonNil(Example(*tmp.Alias)) // Copies all the non-nil fields from the passed Example instance

    return nil
}

tmp after calling unmarshal() doesn't contain any fields but unexportedField — other fields seems to be omitted.

The reproduced issue on Go Playground (although it isn't working due to the dependencies): https://play.golang.org/p/XZg7tEPGXna

Upvotes: 4

Views: 3749

Answers (1)

wgoodall01
wgoodall01

Reputation: 1878

Because most Go unmarshalling packages (including the encoding/* packages) use the reflect package to get at struct fields, and reflect can't access unexported struct fields, the unmarshaler can't parse into unexported fields.

That said, there is still a way to do it. You can unmarshall the YAML into an an unexported type with public fields, which can then be embedded into an exported type. Getters and setters in the same package can just use the embedded struct.

For example:

// Define the types...

type innerData struct {
    ExportedField string
    unexportedField string
}

type Data struct {
    innerData
}


// and then...

d := Data{}
DoSomeUnmarshalling(yamlbytes, &d.innerData)


// and then you can use getters/setters/whatever on `Data`

func (d *Data) GetUnexported() string {
    return d.innerData.unexportedField;
}

(warning: completely untested)

See JSON and dealing with unexported fields for reference.

Upvotes: 2

Related Questions