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

marshal opts #982

Open
wants to merge 1 commit into
base: v3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,7 +876,7 @@ func isStringMap(n *Node) bool {
}

func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
sinfo, err := getStructInfo(out.Type())
sinfo, err := getStructInfo(out.Type(), false)
if err != nil {
panic(err)
}
Expand Down
37 changes: 25 additions & 12 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,37 @@ import (
)

type encoder struct {
emitter yaml_emitter_t
event yaml_event_t
out []byte
flow bool
indent int
doneInit bool
emitter yaml_emitter_t
event yaml_event_t
out []byte
flow bool
indent int
doneInit bool
originalCase bool
arg any
}

func newEncoder() *encoder {
e := &encoder{}
func newEncoder_(arg any, opts ...MarshalOpt) (e *encoder) {
e = &encoder{}
e.arg = arg
for _, o := range opts {
if o == OriginalCase {
e.originalCase = true
}
}
return
}

func newEncoder(arg any, opts ...MarshalOpt) *encoder {
e := newEncoder_(arg, opts...)
yaml_emitter_initialize(&e.emitter)
yaml_emitter_set_output_string(&e.emitter, &e.out)
yaml_emitter_set_unicode(&e.emitter, true)
return e
}

func newEncoderWithWriter(w io.Writer) *encoder {
e := &encoder{}
func newEncoderWithWriter(w io.Writer, arg any, opts ...MarshalOpt) *encoder {
e := newEncoder_(arg, opts...)
yaml_emitter_initialize(&e.emitter)
yaml_emitter_set_output_writer(&e.emitter, w)
yaml_emitter_set_unicode(&e.emitter, true)
Expand Down Expand Up @@ -137,7 +150,7 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
e.stringv(tag, reflect.ValueOf(value.String()))
return
case Marshaler:
v, err := value.MarshalYAML()
v, err := value.MarshalYAML(e.arg)
if err != nil {
fail(err)
}
Expand Down Expand Up @@ -212,7 +225,7 @@ func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Valu
}

func (e *encoder) structv(tag string, in reflect.Value) {
sinfo, err := getStructInfo(in.Type())
sinfo, err := getStructInfo(in.Type(), e.originalCase)
if err != nil {
panic(err)
}
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module "gopkg.in/yaml.v3"
module gopkg.in/yaml.v3

require (
"gopkg.in/check.v1" v0.0.0-20161208181325-20d25e280405
)
go 1.19

require gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
111 changes: 64 additions & 47 deletions yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
//
// Source code and other details for the project are available at GitHub:
//
// https://github.com/go-yaml/yaml
//
// https://github.com/go-yaml/yaml
package yaml

import (
Expand Down Expand Up @@ -48,7 +47,7 @@ type obsoleteUnmarshaler interface {
// If an error is returned by MarshalYAML, the marshaling procedure stops
// and returns with the provided error.
type Marshaler interface {
MarshalYAML() (interface{}, error)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a major breaking change.

Copy link
Author

@aathan aathan Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm no longer working on the downstream project and do not have time to make improvements/changes to the project. I'm sure there is a way to integrate these ideas that "breaks" the existing API less, while using most of the implementation I've already provided. YAML marshaling is much more likely to need "modifiers" (options) than typical simple json marshaling, so I think you should consider adding such features. E.g. perhaps the marshaler could look for an alternative more capable interface (Marshaler2) and use it if it's available.

MarshalYAML(arg any) (interface{}, error)
}

// Unmarshal decodes the first document found within the in byte slice
Expand All @@ -75,16 +74,15 @@ type Marshaler interface {
//
// For example:
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// var t T
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// var t T
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
//
// See the documentation of Marshal for the format of tags and a list of
// supported tag options.
//
func Unmarshal(in []byte, out interface{}) (err error) {
return unmarshal(in, out, false)
}
Expand Down Expand Up @@ -185,46 +183,55 @@ func unmarshal(in []byte, out interface{}, strict bool) (err error) {
//
// The field tag format accepted is:
//
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
//
// The following flags are currently supported:
//
// omitempty Only include the field if it's not set to the zero
// value for the type or to empty slices or maps.
// Zero valued structs will be omitted if all their public
// fields are zero, unless they implement an IsZero
// method (see the IsZeroer interface type), in which
// case the field will be excluded if IsZero returns true.
// omitempty Only include the field if it's not set to the zero
// value for the type or to empty slices or maps.
// Zero valued structs will be omitted if all their public
// fields are zero, unless they implement an IsZero
// method (see the IsZeroer interface type), in which
// case the field will be excluded if IsZero returns true.
//
// flow Marshal using a flow style (useful for structs,
// sequences and maps).
// flow Marshal using a flow style (useful for structs,
// sequences and maps).
//
// inline Inline the field, which must be a struct or a map,
// causing all of its fields or keys to be processed as if
// they were part of the outer struct. For maps, keys must
// not conflict with the yaml keys of other struct fields.
// inline Inline the field, which must be a struct or a map,
// causing all of its fields or keys to be processed as if
// they were part of the outer struct. For maps, keys must
// not conflict with the yaml keys of other struct fields.
//
// In addition, if the key is "-", the field is ignored.
//
// For example:
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
func Marshal(in interface{}) (out []byte, err error) {
return MarshalWithOpts(in, nil)
}

func MarshalWithOpts(in interface{}, arg any, opts ...MarshalOpt) (out []byte, err error) {
defer handleErr(&err)
e := newEncoder()
e := newEncoder(arg, opts...)
defer e.destroy()
e.marshalDoc("", reflect.ValueOf(in))
e.finish()
out = e.out
return
}

type MarshalOpt int

const (
OriginalCase MarshalOpt = 1
)

// An Encoder writes YAML values to an output stream.
type Encoder struct {
encoder *encoder
Expand All @@ -234,8 +241,12 @@ type Encoder struct {
// The Encoder should be closed after use to flush all data
// to w.
func NewEncoder(w io.Writer) *Encoder {
return NewEncoderWithOpts(w, nil)
}

func NewEncoderWithOpts(w io.Writer, arg any, opts ...MarshalOpt) *Encoder {
return &Encoder{
encoder: newEncoderWithWriter(w),
encoder: newEncoderWithWriter(w, arg, opts...),
}
}

Expand All @@ -257,8 +268,12 @@ func (e *Encoder) Encode(v interface{}) (err error) {
// See the documentation for Marshal for details about the
// conversion of Go values into YAML.
func (n *Node) Encode(v interface{}) (err error) {
return n.EncodeWithOpts(v, nil)
}

func (n *Node) EncodeWithOpts(v interface{}, arg any, opts ...MarshalOpt) (err error) {
defer handleErr(&err)
e := newEncoder()
e := newEncoder(arg, opts...)
defer e.destroy()
e.marshalDoc("", reflect.ValueOf(v))
e.finish()
Expand Down Expand Up @@ -358,22 +373,21 @@ const (
//
// For example:
//
// var person struct {
// Name string
// Address yaml.Node
// }
// err := yaml.Unmarshal(data, &person)
//
// Or by itself:
// var person struct {
// Name string
// Address yaml.Node
// }
// err := yaml.Unmarshal(data, &person)
//
// var person Node
// err := yaml.Unmarshal(data, &person)
// Or by itself:
//
// var person Node
// err := yaml.Unmarshal(data, &person)
type Node struct {
// Kind defines whether the node is a document, a mapping, a sequence,
// a scalar value, or an alias to another node. The specific data type of
// scalar nodes may be obtained via the ShortTag and LongTag methods.
Kind Kind
Kind Kind

// Style allows customizing the apperance of the node in the tree.
Style Style
Expand Down Expand Up @@ -421,7 +435,6 @@ func (n *Node) IsZero() bool {
n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0
}


// LongTag returns the long form of the tag that indicates the data type for
// the node. If the Tag field isn't explicitly defined, one will be computed
// based on the node properties.
Expand Down Expand Up @@ -524,7 +537,7 @@ func init() {
unmarshalerType = reflect.ValueOf(&v).Elem().Type()
}

func getStructInfo(st reflect.Type) (*structInfo, error) {
func getStructInfo(st reflect.Type, originalCase bool) (*structInfo, error) {
fieldMapMutex.RLock()
sinfo, found := structMap[st]
fieldMapMutex.RUnlock()
Expand Down Expand Up @@ -592,7 +605,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
if reflect.PtrTo(ftype).Implements(unmarshalerType) {
inlineUnmarshalers = append(inlineUnmarshalers, []int{i})
} else {
sinfo, err := getStructInfo(ftype)
sinfo, err := getStructInfo(ftype, originalCase)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -623,7 +636,11 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
if tag != "" {
info.Key = tag
} else {
info.Key = strings.ToLower(field.Name)
if originalCase {
info.Key = field.Name
} else {
info.Key = strings.ToLower(field.Name)
}
}

if _, found = fieldsMap[info.Key]; found {
Expand Down