mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-10-20 20:40:03 +00:00
Write own yaml2json func (#570)
* fix regression of #384 * add more tests
This commit is contained in:
@@ -4,65 +4,72 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Source: https://github.com/icza/dyno/blob/f1bafe5d99965c48cc9d5c7cf024eeb495facc1e/dyno.go#L563-L601
|
||||
// License: Apache 2.0 - Copyright 2017 Andras Belicza
|
||||
// ConvertMapI2MapS walks the given dynamic object recursively, and
|
||||
// converts maps with interface{} key type to maps with string key type.
|
||||
// This function comes handy if you want to marshal a dynamic object into
|
||||
// JSON where maps with interface{} key type are not allowed.
|
||||
//
|
||||
// Recursion is implemented into values of the following types:
|
||||
// -map[interface{}]interface{}
|
||||
// -map[string]interface{}
|
||||
// -[]interface{}
|
||||
//
|
||||
// When converting map[interface{}]interface{} to map[string]interface{},
|
||||
// toJSON convert gopkg.in/yaml.v3 nodes to object that can be serialized as json
|
||||
// fmt.Sprint() with default formatting is used to convert the key to a string key.
|
||||
func convertMapI2MapS(v interface{}) interface{} {
|
||||
switch x := v.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
m := map[string]interface{}{}
|
||||
for k, v2 := range x {
|
||||
switch k2 := k.(type) {
|
||||
case string: // Fast check if it's already a string
|
||||
m[k2] = convertMapI2MapS(v2)
|
||||
default:
|
||||
m[fmt.Sprint(k)] = convertMapI2MapS(v2)
|
||||
func toJSON(node *yaml.Node) (interface{}, error) {
|
||||
switch node.Kind {
|
||||
case yaml.DocumentNode:
|
||||
return toJSON(node.Content[0])
|
||||
|
||||
case yaml.SequenceNode:
|
||||
val := make([]interface{}, len(node.Content))
|
||||
var err error
|
||||
for i := range node.Content {
|
||||
if val[i], err = toJSON(node.Content[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
v = m
|
||||
return val, nil
|
||||
|
||||
case []interface{}:
|
||||
for i, v2 := range x {
|
||||
x[i] = convertMapI2MapS(v2)
|
||||
case yaml.MappingNode:
|
||||
if (len(node.Content) % 2) != 0 {
|
||||
return nil, fmt.Errorf("broken mapping node")
|
||||
}
|
||||
val := make(map[string]interface{}, len(node.Content)%2)
|
||||
for i := len(node.Content); i > 1; i = i - 2 {
|
||||
k, err := toJSON(node.Content[i-2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if val[fmt.Sprint(k)], err = toJSON(node.Content[i-1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return val, nil
|
||||
|
||||
case map[string]interface{}:
|
||||
for k, v2 := range x {
|
||||
x[k] = convertMapI2MapS(v2)
|
||||
case yaml.ScalarNode:
|
||||
switch node.Tag {
|
||||
case nullTag:
|
||||
return nil, nil
|
||||
case boolTag:
|
||||
return strconv.ParseBool(node.Value)
|
||||
case intTag:
|
||||
return strconv.ParseInt(node.Value, 10, 64)
|
||||
case floatTag:
|
||||
return strconv.ParseFloat(node.Value, 64)
|
||||
}
|
||||
return node.Value, nil
|
||||
}
|
||||
|
||||
return v
|
||||
return nil, fmt.Errorf("do not support yaml node kind '%v'", node.Kind)
|
||||
}
|
||||
|
||||
func Yml2Json(data []byte) (j []byte, err error) {
|
||||
m := make(map[interface{}]interface{})
|
||||
err = yaml.Unmarshal(data, &m)
|
||||
if err != nil {
|
||||
func ToJSON(data []byte) ([]byte, error) {
|
||||
m := &yaml.Node{}
|
||||
if err := yaml.Unmarshal(data, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
j, err = json.Marshal(convertMapI2MapS(m))
|
||||
d, err := toJSON(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return j, nil
|
||||
return json.Marshal(d)
|
||||
}
|
||||
|
||||
func LoadYmlFileAsJSON(path string) (j []byte, err error) {
|
||||
@@ -71,10 +78,24 @@ func LoadYmlFileAsJSON(path string) (j []byte, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
j, err = Yml2Json(data)
|
||||
j, err = ToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
// Source: https://github.com/go-yaml/yaml/blob/3e3283e801afc229479d5fc68aa41df1137b8394/resolve.go#L70-L81
|
||||
const (
|
||||
nullTag = "!!null"
|
||||
boolTag = "!!bool"
|
||||
intTag = "!!int"
|
||||
floatTag = "!!float"
|
||||
// strTag = "!!str" // we dont have to parse it
|
||||
// timestampTag = "!!timestamp" // TODO: do we have to parse this?
|
||||
// seqTag = "!!seq" // TODO: do we have to parse this?
|
||||
// mapTag = "!!map" // TODO: do we have to parse this?
|
||||
// binaryTag = "!!binary" // TODO: do we have to parse this?
|
||||
// mergeTag = "!!merge" // TODO: do we have to parse this?
|
||||
)
|
||||
|
||||
45
shared/yml/yml_test.go
Normal file
45
shared/yml/yml_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package yml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
yaml string
|
||||
json string
|
||||
}{{
|
||||
yaml: `- name: Jack
|
||||
- name: Jill
|
||||
`,
|
||||
json: `[{"name":"Jack"},{"name":"Jill"}]`,
|
||||
}, {
|
||||
yaml: `name: Jack`,
|
||||
json: `{"name":"Jack"}`,
|
||||
}, {
|
||||
yaml: `name: Jack
|
||||
job: Butcher
|
||||
`,
|
||||
json: `{"job":"Butcher","name":"Jack"}`,
|
||||
}, {
|
||||
yaml: `- name: Jack
|
||||
job: Butcher
|
||||
- name: Jill
|
||||
job: Cook
|
||||
obj:
|
||||
empty: false
|
||||
data: |
|
||||
some data 123
|
||||
with new line
|
||||
`,
|
||||
json: `[{"job":"Butcher","name":"Jack"},{"job":"Cook","name":"Jill","obj":{"data":"some data 123\nwith new line\n","empty":false}}]`,
|
||||
}}
|
||||
|
||||
for _, tc := range tests {
|
||||
result, err := ToJSON([]byte(tc.yaml))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, tc.json, string(result))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user