Skip to content

Commit

Permalink
Merge pull request #106 from goccy/feature/use-ordered-map
Browse files Browse the repository at this point in the history
Add UseOrderedMap option
  • Loading branch information
goccy authored May 29, 2020
2 parents ca90bf9 + fe875b3 commit 0a1c649
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 0 deletions.
36 changes: 36 additions & 0 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Decoder struct {
isResolvedReference bool
validator StructValidator
disallowUnknownField bool
useOrderedMap bool
}

// NewDecoder returns a new decoder that reads from r.
Expand All @@ -49,6 +50,7 @@ func NewDecoder(r io.Reader, opts ...DecodeOption) *Decoder {
isRecursiveDir: false,
isResolvedReference: false,
disallowUnknownField: false,
useOrderedMap: false,
}
}

Expand Down Expand Up @@ -105,6 +107,25 @@ func (d *Decoder) setToMapValue(node ast.Node, m map[string]interface{}) {
}
}

func (d *Decoder) setToOrderedMapValue(node ast.Node, m *MapSlice) {
switch n := node.(type) {
case *ast.MappingValueNode:
if n.Key.Type() == ast.MergeKeyType && n.Value.Type() == ast.AliasType {
aliasNode := n.Value.(*ast.AliasNode)
aliasName := aliasNode.Value.GetToken().Value
node := d.anchorNodeMap[aliasName]
d.setToOrderedMapValue(node, m)
} else {
key := n.Key.GetToken().Value
*m = append(*m, MapItem{Key: key, Value: d.nodeToValue(n.Value)})
}
case *ast.MappingNode:
for _, value := range n.Values {
d.setToOrderedMapValue(value, m)
}
}
}

func (d *Decoder) nodeToValue(node ast.Node) interface{} {
switch n := node.(type) {
case *ast.NullNode:
Expand Down Expand Up @@ -150,15 +171,30 @@ func (d *Decoder) nodeToValue(node ast.Node) interface{} {
aliasNode := n.Value.(*ast.AliasNode)
aliasName := aliasNode.Value.GetToken().Value
node := d.anchorNodeMap[aliasName]
if d.useOrderedMap {
m := MapSlice{}
d.setToOrderedMapValue(node, &m)
return m
}
m := map[string]interface{}{}
d.setToMapValue(node, m)
return m
}
key := n.Key.GetToken().Value
if d.useOrderedMap {
return MapSlice{{Key: key, Value: d.nodeToValue(n.Value)}}
}
return map[string]interface{}{
key: d.nodeToValue(n.Value),
}
case *ast.MappingNode:
if d.useOrderedMap {
m := make(MapSlice, 0, len(n.Values))
for _, value := range n.Values {
d.setToOrderedMapValue(value, &m)
}
return m
}
m := make(map[string]interface{}, len(n.Values))
for _, value := range n.Values {
d.setToMapValue(value, m)
Expand Down
25 changes: 25 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1703,3 +1703,28 @@ value: |
t.Fatal("failed to unmarshal literal with bytes unmarshaler")
}
}

func TestDecoder_UseOrderedMap(t *testing.T) {
yml := `
a: b
c: d
e:
f: g
h: i
j: k
`
var v interface{}
if err := yaml.NewDecoder(strings.NewReader(yml), yaml.UseOrderedMap()).Decode(&v); err != nil {
t.Fatalf("%+v", err)
}
if _, ok := v.(yaml.MapSlice); !ok {
t.Fatalf("failed to convert to ordered map: %T", v)
}
bytes, err := yaml.Marshal(v)
if err != nil {
t.Fatalf("%+v", err)
}
if string(yml) != "\n"+string(bytes) {
t.Fatalf("expected:[%s] actual:[%s]", string(yml), "\n"+string(bytes))
}
}
9 changes: 9 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ func DisallowUnknownField() DecodeOption {
}
}

// UseOrderedMap can be interpreted as a map,
// and uses MapSlice ( ordered map ) aggressively if there is no type specification
func UseOrderedMap() DecodeOption {
return func(d *Decoder) error {
d.useOrderedMap = true
return nil
}
}

// EncodeOption functional option type for Encoder
type EncodeOption func(e *Encoder) error

Expand Down

0 comments on commit 0a1c649

Please sign in to comment.