Skip to content
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
56 changes: 56 additions & 0 deletions conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"fmt"
"net/url"
"strconv"

"gopkg.in/yaml.v3"
)

// SignatureSeparator is the character that is used to
Expand Down Expand Up @@ -99,16 +101,28 @@ func cleanUpMapArray(in []Map) []Map {
return result
}

func cleanUpMIIArray(in []map[interface{}]interface{}) []Map {
result := make([]Map, len(in))
for i, v := range in {
result[i] = cleanUpInterfaceMap(v)
}
return result
}

func cleanUp(v interface{}) interface{} {
switch v := v.(type) {
case []interface{}:
return cleanUpInterfaceArray(v)
case []map[string]interface{}:
return cleanUpMSIArray(v)
case []map[interface{}]interface{}:
return cleanUpMIIArray(v)
case map[interface{}]interface{}:
return cleanUpInterfaceMap(v)
case Map:
return cleanUpStringMap(v)
case map[string]interface{}:
return cleanUpStringMap(v)
case []Map:
return cleanUpMapArray(v)
default:
Expand All @@ -126,6 +140,48 @@ func (m Map) MustJSON() string {
return result
}

// YAML converts the contained object to a YAML string
// representation
func (m Map) YAML() (resultStr string, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("objx: YAML encode failed with: %v", r)
}
}()

for k, v := range m {
m[k] = cleanUp(v)
}

result, err := yaml.Marshal(m)
if err != nil {
err = errors.New("objx: YAML encode failed with: " + err.Error())
}
return string(result), err
}

// MustYAML converts the contained object to a YAML string
// representation and panics if there is an error
func (m Map) MustYAML() string {
result, err := m.YAML()
if err != nil {
panic(err.Error())
}
return result
}

// Yaml converts the contained object to a YAML string
// representation. Alias for YAML.
func (m Map) Yaml() (string, error) {
return m.YAML()
}

// MustYaml converts the contained object to a YAML string
// representation and panics if there is an error. Alias for MustYAML.
func (m Map) MustYaml() string {
return m.MustYAML()
}

// Base64 converts the contained object to a Base64 string
// representation of the JSON string representation
func (m Map) Base64() (string, error) {
Expand Down
59 changes: 59 additions & 0 deletions conversions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,65 @@ func TestConversionJSONWithError(t *testing.T) {
assert.Error(t, err)
}

func TestConversionYAML(t *testing.T) {
o := objx.Map{"name": "Mat", "age": 30}

result, err := o.YAML()
require.NoError(t, err)
require.NotNil(t, result)

parsed, err := objx.FromYAML(result)
require.NoError(t, err)
assert.Equal(t, "Mat", parsed.Get("name").Str())
assert.Equal(t, 30, parsed.Get("age").Int())

assert.Equal(t, result, o.MustYAML())
assert.Equal(t, result, o.MustYaml())

aliasResult, err := o.Yaml()
require.NoError(t, err)
assert.Equal(t, result, aliasResult)

i := objx.Map{
"a": map[interface{}]interface{}{"b": objx.Map{"c": map[interface{}]interface{}{"d": "e"}},
"f": []objx.Map{{"g": map[interface{}]interface{}{"h": "i"}}},
"j": []map[string]interface{}{{"k": map[interface{}]interface{}{"l": "m"}}},
"n": []interface{}{objx.Map{"o": "p"}},
},
}

result, err = i.YAML()
require.NoError(t, err)
require.NotNil(t, result)
assert.Equal(t, result, i.MustYAML())

parsedNested, err := objx.FromYAML(result)
require.NoError(t, err)
assert.Equal(t, "e", parsedNested.Get("a.b.c.d").Str())
assert.Equal(t, "i", parsedNested.Get("a.f[0].g.h").Str())
assert.Equal(t, "m", parsedNested.Get("a.j[0].k.l").Str())
assert.Equal(t, "p", parsedNested.Get("a.n[0].o").Str())
}

func TestConversionYAMLWithError(t *testing.T) {
o := objx.MSI()
o["test"] = func() {}

assert.Panics(t, func() {
o.MustYAML()
})

assert.Panics(t, func() {
o.MustYaml()
})

_, err := o.YAML()
assert.Error(t, err)

_, err = o.Yaml()
assert.Error(t, err)
}

func TestConversionBase64(t *testing.T) {
o := objx.Map{"name": "Mat"}

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/stretchr/objx

go 1.20

require gopkg.in/yaml.v3 v3.0.1
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
94 changes: 94 additions & 0 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/url"
"strings"

"gopkg.in/yaml.v3"
)

// MSIConvertable is an interface that defines methods for converting your
Expand Down Expand Up @@ -129,6 +132,97 @@ func FromJSONSlice(jsonString string) ([]Map, error) {
return slice, nil
}

// MustFromYAML creates a new Map containing the data specified in the
// yamlString.
//
// Panics if the YAML is invalid.
func MustFromYAML(yamlString string) Map {
o, err := FromYAML(yamlString)
if err != nil {
panic("objx: MustFromYAML failed with error: " + err.Error())
}
return o
}

// MustFromYaml is an alias for MustFromYAML.
func MustFromYaml(yamlString string) Map {
return MustFromYAML(yamlString)
}

// MustFromYAMLSlice creates a new slice of Map containing the data specified in the
// yamlString. Works with YAML with a top level array.
//
// Panics if the YAML is invalid.
func MustFromYAMLSlice(yamlString string) []Map {
slice, err := FromYAMLSlice(yamlString)
if err != nil {
panic("objx: MustFromYAMLSlice failed with error: " + err.Error())
}
return slice
}

// MustFromYamlSlice is an alias for MustFromYAMLSlice.
func MustFromYamlSlice(yamlString string) []Map {
return MustFromYAMLSlice(yamlString)
}

// FromYAML creates a new Map containing the data specified in the
// yamlString.
//
// Returns an error if the YAML is invalid.
func FromYAML(yamlString string) (m Map, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("objx: YAML decode failed with: %v", r)
m = Nil
}
}()

err = yaml.Unmarshal([]byte(yamlString), &m)
if err != nil {
return Nil, err
}
for k, v := range m {
m[k] = cleanUp(v)
}
return m, nil
}

// FromYaml is an alias for FromYAML.
func FromYaml(yamlString string) (Map, error) {
return FromYAML(yamlString)
}

// FromYAMLSlice creates a new slice of Map containing the data specified in the
// yamlString. Works with YAML with a top level array.
//
// Returns an error if the YAML is invalid.
func FromYAMLSlice(yamlString string) (slice []Map, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("objx: YAML decode failed with: %v", r)
slice = nil
}
}()

err = yaml.Unmarshal([]byte(yamlString), &slice)
if err != nil {
return nil, err
}
for i, sm := range slice {
for k, v := range sm {
sm[k] = cleanUp(v)
}
slice[i] = sm
}
return slice, nil
}

// FromYamlSlice is an alias for FromYAMLSlice.
func FromYamlSlice(yamlString string) ([]Map, error) {
return FromYAMLSlice(yamlString)
}

// FromBase64 creates a new Obj containing the data specified
// in the Base64 string.
//
Expand Down
109 changes: 109 additions & 0 deletions map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,112 @@ func TestJSONTopLevelSliceWithError(t *testing.T) {
_ = objx.MustFromJSONSlice(`{"id": 10000001}`)
})
}

func TestMapFromYAML(t *testing.T) {
yamlString := "name: Mat\nage: 30\n"
o, err := objx.FromYAML(yamlString)

require.NoError(t, err)
require.NotNil(t, o)
assert.Equal(t, "Mat", o["name"])
assert.Equal(t, 30, o["age"])
assert.Equal(t, "Mat", o.Get("name").Str())
assert.Equal(t, 30, o.Get("age").Int())

// Test alias FromYaml
o2, err := objx.FromYaml(yamlString)
require.NoError(t, err)
require.NotNil(t, o2)
assert.Equal(t, "Mat", o2.Get("name").Str())
}

func TestMapMustFromYAML(t *testing.T) {
yamlString := "name: Mat\nage: 30\n"
o := objx.MustFromYAML(yamlString)

require.NotNil(t, o)
assert.Equal(t, "Mat", o.Get("name").Str())
assert.Equal(t, 30, o.Get("age").Int())

// Test alias MustFromYaml
o2 := objx.MustFromYaml(yamlString)
require.NotNil(t, o2)
assert.Equal(t, "Mat", o2.Get("name").Str())
}

func TestMapFromYAMLWithError(t *testing.T) {
_, err := objx.FromYAML(":\n invalid: yaml: :")
assert.Error(t, err)

assert.Panics(t, func() {
objx.MustFromYAML(":\n invalid: yaml: :")
})

assert.Panics(t, func() {
objx.MustFromYaml(":\n invalid: yaml: :")
})
}

func TestMapFromYAMLNested(t *testing.T) {
yamlString := `
user:
name: Tyler
address:
city: Salt Lake City
state: UT
tags:
- admin
- developer
`
m, err := objx.FromYAML(yamlString)
require.NoError(t, err)
require.NotNil(t, m)

assert.Equal(t, "Tyler", m.Get("user.name").Str())
assert.Equal(t, "Salt Lake City", m.Get("user.address.city").Str())
assert.Equal(t, "UT", m.Get("user.address.state").Str())
assert.Equal(t, "admin", m.Get("user.tags[0]").Str())
assert.Equal(t, "developer", m.Get("user.tags[1]").Str())
}

func TestYAMLTopLevelSlice(t *testing.T) {
yamlString := `
- id: 10000001
name: Item 1
- id: 42
name: Item 2
`
slice, err := objx.FromYAMLSlice(yamlString)

assert.NoError(t, err)
require.Len(t, slice, 2)
assert.Equal(t, 10000001, slice[0].Get("id").MustInt())
assert.Equal(t, "Item 1", slice[0].Get("name").Str())
assert.Equal(t, 42, slice[1].Get("id").MustInt())
assert.Equal(t, "Item 2", slice[1].Get("name").Str())

mustSlice := objx.MustFromYAMLSlice(yamlString)
require.Len(t, mustSlice, 2)
assert.Equal(t, 10000001, mustSlice[0].Get("id").MustInt())

// Test aliases
aliasSlice, err := objx.FromYamlSlice(yamlString)
assert.NoError(t, err)
require.Len(t, aliasSlice, 2)

mustAliasSlice := objx.MustFromYamlSlice(yamlString)
require.Len(t, mustAliasSlice, 2)
}

func TestYAMLTopLevelSliceWithError(t *testing.T) {
slice, err := objx.FromYAMLSlice(":\n invalid: yaml: :")

assert.Error(t, err)
assert.Nil(t, slice)
assert.Panics(t, func() {
_ = objx.MustFromYAMLSlice(":\n invalid: yaml: :")
})
assert.Panics(t, func() {
_ = objx.MustFromYamlSlice(":\n invalid: yaml: :")
})
}