Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Structs that unmarshal themselves don't work #107

Closed
rojer opened this issue Jun 15, 2015 · 4 comments
Closed

Structs that unmarshal themselves don't work #107

rojer opened this issue Jun 15, 2015 · 4 comments

Comments

@rojer
Copy link

rojer commented Jun 15, 2015

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
@niemeyer
Copy link
Contributor

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.

@mykter
Copy link

mykter commented Aug 14, 2019

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.

@rojer
Copy link
Author

rojer commented Aug 14, 2019

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

@ewrenn8
Copy link

ewrenn8 commented Aug 26, 2019

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants