Skip to content

type that implements BytesUnmarshaler with complex data has errors #638

@cpuguy83

Description

@cpuguy83

Below is some test cases I'm using to pinpoint an issue I'm seeing when using BytesUnmarshaler.
It seems like the main problem happens when there are yaml anchors?

I was actually trying to reproduce another issue (but still w/ BytesUnmarshaler) that cropped up while using anchors that actually causes the parsed yaml to produce an error when reparsing (from *ast.File.String())
In the test cases here the parsed yaml produced by *ast.File.String() is not invalid yaml so there is no error however the data is incorrect.

Running the below tests:

$ go test .                                                                                                       ─╯
--- FAIL: TestComplexWithBytesUnmarshaler (0.00s)
    main_test.go:56: expected 2, got 0
FAIL
FAIL    test    0.211s
FAIL
Test cases
package main

import (
	"fmt"
	"testing"

	"github.com/goccy/go-yaml"
	"github.com/goccy/go-yaml/parser"
)

var complexTestData = []byte(`
x-foo: &data
  bar:
    - one
    - two

foo:
  stuff: *data
`)

var simpleTestData = []byte(`
foo:
  - one
  - two
`)

func (t *ComplexWithBytesUnmarshaler) UnmarshalYAML(dt []byte) error {
	type intermediate ComplexWithBytesUnmarshaler
	var i intermediate

	if err := unmarshal(&i, dt); err != nil {
		return err
	}

	*t = ComplexWithBytesUnmarshaler(i)
	return nil
}

func unmarshal[T any](target *T, dt []byte) error {
	parsed, err := parser.ParseBytes(dt, 0)
	if err != nil {
		return fmt.Errorf("error parsing: %w", err)
	}

	return yaml.NewDecoder(parsed).Decode(target)
}

func TestComplexWithBytesUnmarshaler(t *testing.T) {
	var c ComplexWithBytesUnmarshaler

	if err := yaml.Unmarshal(complexTestData, &c); err != nil {
		t.Fatalf("error unmarshalling: %v", err)
	}

	if len(c.Foo.Stuff.Bar) != 2 {
		t.Fatalf("expected 2, got %d", len(c.Foo.Stuff.Bar))
	}

	if c.Foo.Stuff.Bar[0] != "one" {
		t.Fatalf("expected one, got %s", c.Foo.Stuff.Bar[0])
	}

	if c.Foo.Stuff.Bar[1] != "two" {
		t.Fatalf("expected two, got %s", c.Foo.Stuff.Bar[1])
	}
}

func checkSliceData(t *testing.T, dt []string) {
	t.Helper()

	if len(dt) != 2 {
		t.Fatalf("expected 2, got %d", len(dt))
	}

	if dt[0] != "one" {
		t.Fatalf("expected one, got %s", dt[0])
	}

	if dt[1] != "two" {
		t.Fatalf("expected two, got %s", dt[1])
	}
}

func TestComplexNoCustomUnmarshaller(t *testing.T) {
	var c ComplexNoCustomUnmarshaller

	if err := yaml.Unmarshal(complexTestData, &c); err != nil {
		t.Fatalf("error unmarshalling: %v", err)
	}

	checkSliceData(t, c.Foo.Stuff.Bar)

	// check again with the custom marshal implementation
	c = ComplexNoCustomUnmarshaller{}
	if err := unmarshal(&c, complexTestData); err != nil {
		t.Fatalf("error unmarshalling: %v", err)
	}
	checkSliceData(t, c.Foo.Stuff.Bar)

	t.Run("with intermediate type", func(t *testing.T) {
		type intermediate ComplexNoCustomUnmarshaller
		var i intermediate

		if err := unmarshal(&i, complexTestData); err != nil {
			t.Fatal(err)
		}

		c2 := &ComplexNoCustomUnmarshaller{}
		*c2 = ComplexNoCustomUnmarshaller(i)
		checkSliceData(t, c2.Foo.Stuff.Bar)
	})
}

func TestSimpleWithBytesUnmarshaler(t *testing.T) {
	var s SimpleWithBytesUnmarshaler

	if err := yaml.Unmarshal(simpleTestData, &s); err != nil {
		t.Fatalf("error unmarshalling: %v", err)
	}
	checkSliceData(t, s.Foo)
}

func TestSimpleNoCustomUnmarshaller(t *testing.T) {
	var s SimpleNoCustomUnmarshaller

	if err := unmarshal(&s, simpleTestData); err != nil {
		t.Fatalf("error unmarshalling: %v", err)
	}

	checkSliceData(t, s.Foo)

	// check again with the custom marshal implementation
	s = SimpleNoCustomUnmarshaller{}
	if err := unmarshal(&s, simpleTestData); err != nil {
		t.Fatalf("error unmarshalling: %v", err)
	}
	checkSliceData(t, s.Foo)

	t.Run("with intermediate type", func(t *testing.T) {
		type intermediate SimpleNoCustomUnmarshaller
		var i intermediate

		if err := unmarshal(&i, simpleTestData); err != nil {
			t.Fatal(err)
		}

		s := &SimpleNoCustomUnmarshaller{}
		*s = SimpleNoCustomUnmarshaller(i)
		checkSliceData(t, s.Foo)
	})
}

type SimpleWithBytesUnmarshaler struct {
	Foo []string `yaml:"foo"`
}

type SimpleNoCustomUnmarshaller struct {
	Foo []string `yaml:"foo"`
}

type ComplexWithBytesUnmarshaler struct {
	Foo struct {
		Stuff struct {
			Bar []string `yaml:"bar"`
		} `yaml:"stuff"`
	} `yaml:"foo"`
}

type ComplexNoCustomUnmarshaller struct {
	Foo struct {
		Stuff struct {
			Bar []string `yaml:"bar"`
		} `yaml:"stuff"`
	} `yaml:"foo"`
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions