Skip to content

Multi-line text fields corrupted when using custom marshalers #845

@wrouesnel

Description

@wrouesnel

https://go.dev/play/p/zEajxshUE_y

I have a reliable bug from the following YAML snippet, which is output of a custom encoder:

container:
  type: generic
  config:
    message: |-
    some test text
    second line of the text

Note the corrupted text. The inner struct, if marshaled properly should produce output like:

container:
  type: generic
  config:
    message: |-
      some test text
      second line of the text

When it hits this line:

if e.isMapNode(encoded) {
and e.AddColumn is called, the multiline text field indentation is corrupted.

This only happens when a custom encoder is embedding the output into the key of another struct. The following program reproduces the problem:

package main

import (
	"bytes"
	"fmt"

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

type D struct {
	Message string
}

type T struct {
	Type   string
	Config interface{}
}

type O struct {
	Container interface{}
}

func main() {
	marshaler := func(t D) ([]byte, error) {
		return yaml.Marshal(&T{
			"generic",
			t,
		})
	}

	testStruct := &O{&D{
		Message: "some test text\nsecond line of the text",
	}}

	b := bytes.NewBuffer(nil)
	err := yaml.NewEncoder(b, yaml.CustomMarshaler(marshaler)).Encode(testStruct)
	fmt.Println(err)
	fmt.Println(b.String())
}

EDIT:

Further investigation seems to indicate this is a general problem with embedded multiline text in version v1.19.2 from any marshalers - https://go.dev/play/p/sH6LhtCIzAB

package main

import (
	"bytes"
	"fmt"

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

type D struct {
	Message string
}

type X struct {
	Type   string
	Config interface{}
}

func (x *X) MarshalYAML() ([]byte, error) {
	return yaml.Marshal(map[string]interface{}{
		"type":   "generic",
		"config": x.Config,
	})
}

type O struct {
	Container interface{}
}

func main() {
	testStruct3 := &O{
		&X{
			"generic",
			&D{"some test text\nsecond line of the text"},
		},
	}

	b3 := bytes.NewBuffer(nil)
	err := yaml.NewEncoder(b3).Encode(testStruct3)
	fmt.Println(err)
	fmt.Println(b3.String())

}
container:
  config:
    message: |-
    some test text
    second line of the text
  type: generic

EDIT 2: Extremizing the example makes it clear what's happening it looks like -

https://go.dev/play/p/KsNas3IMiVw

package main

import (
	"bytes"
	"fmt"

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

type D struct {
	Message string
}

type X struct {
	Type   string
	Config interface{}
}

func (x *X) MarshalYAML() ([]byte, error) {
	return yaml.Marshal(map[string]interface{}{
		"type":   "generic",
		"config": x.Config,
	})
}

type O struct {
	Container interface{}
}

func main() {
	message := "some test text\nsecond line of the text"
	testStruct3 := &O{&O{&O{
		&O{&O{
			&X{
				"generic",
				&D{message},
			},
		},
		}}},
	}

	b3 := bytes.NewBuffer(nil)
	yaml.NewEncoder(b3).Encode(testStruct3)
	fmt.Println(b3.String())

}
container:
  container:
    container:
      container:
        container:
          config:
            message: |-
    some test text
    second line of the text
          type: generic

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions