Yaml: Structs that unmarshal themselves don't work

Created on 15 Jun 2015  路  4Comments  路  Source: go-yaml/yaml

I'm migrating code from JSON to YAML and have run into an issue with UnmarshalYAML recursing endlessly where UnmarshalJSON works.
I have a struct that has UnmarshalJSON defined and further calls into json.Unmarshal to do most of the unmarshaling, but then does some touch-up/validation afterwards.
Naive implementation does cause endless recursion, but a type alias stops it. See live example here: http://play.golang.org/p/St33ZkWvTN

note foo.ok set to true during unmarshaling which was not in the original message. this is convenient.

now, when converted to equivalent code for YAML, it goes into a recursion loop, as YAML unmarshaler seems to treat type aliases differently than JSON.

it would be nice if this use case was supported.
here's a full test program:

package main

import (
        "encoding/json"
        "fmt"
        "os"

        yaml "gopkg.in/yaml.v2"
)

type Foo struct {
        Bar string
        ok  bool
}

type fooJSON *Foo

func (f *Foo) UnmarshalJSON(data []byte) error {
        fmt.Printf("unmarshal JSON\n")
        if err := json.Unmarshal(data, fooJSON(f)); err != nil {
                return err
        }
        f.ok = true
        return nil
}

type fooYAML *Foo

var n = 0

func (f *Foo) UnmarshalYAML(unmarshal func(interface{}) error) error {
        n += 1
        fmt.Printf("unmarshal YAML\n")
        if n > 1 {
                os.Exit(1)
        }
        if err := unmarshal(fooYAML(f)); err != nil {
                return err
        }
        f.ok = true
        return nil
}

func main() {
        var foo1, foo2 Foo
        err := json.Unmarshal([]byte(`{"bar": "baz"}`), &foo1)
        if err != nil {
                panic(err)
        }
        fmt.Printf("%+v\n", foo1)
        err = yaml.Unmarshal([]byte(`{"bar": "baz"}`), &foo2)
        if err != nil {
                panic(err)
        }
        fmt.Printf("%+v\n", foo2)
}

and output it produces:

unmarshal JSON
{Bar:baz ok:true}
unmarshal YAML
unmarshal YAML

Most helpful comment

Hi @mykter I came across this same issue and was able to work around it by casting the receiver pointer to a pointer to the alias type and then Unmarshaling in to the new pointer like so:

func (f *Foo) UnmarshalYAML(unmarshal func(interface{}) error) error {
       // Do extra actions
       type fooYAML Foo
       fy := (*fooYAML)(f)
       if err := unmarshal(fy); err != nil {
               return err
       }
       return nil
}

Hope it works for you too!

All 4 comments

There's indeed a difference in behavior, but we cannot simply break compatibility to make the behavior of the yaml package match the json package, as that would break the expectation of existing applications trusting on the implemented behavior.

Appreciate this was 4 years ago so the odds aren't great, but.. did you find a work around for this @rojer?
I have exactly the same use case - the default unmarshalling behaviour works well, but I want to add some extra domain-specific validation on top of it.

@mykter i honestly don't remember what i was trying to do at the time and what i ended up doing. sorry.

Hi @mykter I came across this same issue and was able to work around it by casting the receiver pointer to a pointer to the alias type and then Unmarshaling in to the new pointer like so:

func (f *Foo) UnmarshalYAML(unmarshal func(interface{}) error) error {
       // Do extra actions
       type fooYAML Foo
       fy := (*fooYAML)(f)
       if err := unmarshal(fy); err != nil {
               return err
       }
       return nil
}

Hope it works for you too!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rnix picture rnix  路  3Comments

vanloswang picture vanloswang  路  3Comments

mro picture mro  路  13Comments

RichiH picture RichiH  路  7Comments

johscheuer picture johscheuer  路  13Comments