mirror of
https://github.com/goccy/go-yaml.git
synced 2025-12-08 06:09:57 +00:00
Add support for RawMessage, similar to json.RawMessage (#790)
* Add support for RawMessage, similar to json.RawMessage This adds a type that users can use to refer to raw data in the yaml for deferred decoding. Signed-off-by: Brian Goff <cpuguy83@gmail.com> * test: add suggested cases from #668 --------- Signed-off-by: Brian Goff <cpuguy83@gmail.com> Co-authored-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
parent
07c09c0287
commit
a7b4bfbcf4
2 changed files with 279 additions and 2 deletions
31
yaml.go
31
yaml.go
|
|
@ -324,3 +324,34 @@ func RegisterCustomUnmarshalerContext[T any](unmarshaler func(context.Context, *
|
||||||
return unmarshaler(ctx, v.(*T), b)
|
return unmarshaler(ctx, v.(*T), b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawMessage is a raw encoded YAML value. It implements [BytesMarshaler] and
|
||||||
|
// [BytesUnmarshaler] and can be used to delay YAML decoding or precompute a YAML
|
||||||
|
// encoding.
|
||||||
|
// It also implements [json.Marshaler] and [json.Unmarshaler].
|
||||||
|
//
|
||||||
|
// This is similar to [json.RawMessage] in the stdlib.
|
||||||
|
type RawMessage []byte
|
||||||
|
|
||||||
|
func (m RawMessage) MarshalYAML() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RawMessage) UnmarshalYAML(dt []byte) error {
|
||||||
|
if m == nil {
|
||||||
|
return errors.New("yaml.RawMessage: UnmarshalYAML on nil pointer")
|
||||||
|
}
|
||||||
|
*m = append((*m)[0:0], dt...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RawMessage) UnmarshalJSON(b []byte) error {
|
||||||
|
return m.UnmarshalYAML(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||||
|
return YAMLToJSON(m)
|
||||||
|
}
|
||||||
|
|
|
||||||
250
yaml_test.go
250
yaml_test.go
|
|
@ -1,6 +1,7 @@
|
||||||
package yaml_test
|
package yaml_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -78,7 +79,7 @@ foo: bar # comment
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecodeKeepAddress(t *testing.T) {
|
func TestDecodeKeepAddress(t *testing.T) {
|
||||||
var data = `
|
data := `
|
||||||
a: &a [_]
|
a: &a [_]
|
||||||
b: &b [*a,*a]
|
b: &b [*a,*a]
|
||||||
c: &c [*b,*b]
|
c: &c [*b,*b]
|
||||||
|
|
@ -103,7 +104,7 @@ d: &d [*c,*c]
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSmartAnchor(t *testing.T) {
|
func TestSmartAnchor(t *testing.T) {
|
||||||
var data = `
|
data := `
|
||||||
a: &a [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]
|
a: &a [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]
|
||||||
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
|
@ -263,3 +264,248 @@ foo: 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkRawValue[T any](t *testing.T, v yaml.RawMessage, expected T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var actual T
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(v, &actual); err != nil {
|
||||||
|
t.Errorf("failed to unmarshal: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("expected %v, got %v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkJSONRawValue[T any](t *testing.T, v json.RawMessage, expected T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var actual T
|
||||||
|
|
||||||
|
if err := json.Unmarshal(v, &actual); err != nil {
|
||||||
|
t.Errorf("failed to unmarshal: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("expected %v, got %v", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRawValue(t, yaml.RawMessage(v), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRawMessage(t *testing.T) {
|
||||||
|
data := []byte(`
|
||||||
|
a: 1
|
||||||
|
b: "asdf"
|
||||||
|
c:
|
||||||
|
foo: bar
|
||||||
|
`)
|
||||||
|
|
||||||
|
var m map[string]yaml.RawMessage
|
||||||
|
if err := yaml.Unmarshal(data, &m); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m) != 3 {
|
||||||
|
t.Fatalf("failed to decode: %d", len(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRawValue(t, m["a"], 1)
|
||||||
|
checkRawValue(t, m["b"], "asdf")
|
||||||
|
checkRawValue(t, m["c"], map[string]string{"foo": "bar"})
|
||||||
|
|
||||||
|
dt, err := yaml.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var m2 map[string]yaml.RawMessage
|
||||||
|
if err := yaml.Unmarshal(dt, &m2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRawValue(t, m2["a"], 1)
|
||||||
|
checkRawValue(t, m2["b"], "asdf")
|
||||||
|
checkRawValue(t, m2["c"], map[string]string{"foo": "bar"})
|
||||||
|
|
||||||
|
dt, err = json.Marshal(m2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m3 map[string]yaml.RawMessage
|
||||||
|
if err := yaml.Unmarshal(dt, &m3); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
checkRawValue(t, m3["a"], 1)
|
||||||
|
checkRawValue(t, m3["b"], "asdf")
|
||||||
|
checkRawValue(t, m3["c"], map[string]string{"foo": "bar"})
|
||||||
|
|
||||||
|
var m4 map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(dt, &m4); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
checkJSONRawValue(t, m4["a"], 1)
|
||||||
|
checkJSONRawValue(t, m4["b"], "asdf")
|
||||||
|
checkJSONRawValue(t, m4["c"], map[string]string{"foo": "bar"})
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawYAMLWrapper struct {
|
||||||
|
StaticField string `json:"staticField" yaml:"staticField"`
|
||||||
|
DynamicField yaml.RawMessage `json:"dynamicField" yaml:"dynamicField"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawJSONWrapper struct {
|
||||||
|
StaticField string `json:"staticField" yaml:"staticField"`
|
||||||
|
DynamicField json.RawMessage `json:"dynamicField" yaml:"dynamicField"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w rawJSONWrapper) Equals(o *rawJSONWrapper) bool {
|
||||||
|
if w.StaticField != o.StaticField {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return reflect.DeepEqual(w.DynamicField, o.DynamicField)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dynamicField struct {
|
||||||
|
A int `json:"a" yaml:"a"`
|
||||||
|
B string `json:"b" yaml:"b"`
|
||||||
|
C map[string]string `json:"c" yaml:"c"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t dynamicField) Equals(o *dynamicField) bool {
|
||||||
|
if t.A != o.A {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if t.B != o.B {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(t.C) != len(o.C) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for k, v := range t.C {
|
||||||
|
ov, exists := o.C[k]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v != ov {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRawMessageJSONCompatibility(t *testing.T) {
|
||||||
|
rawData := []byte(`staticField: value
|
||||||
|
dynamicField:
|
||||||
|
a: 1
|
||||||
|
b: abcd
|
||||||
|
c:
|
||||||
|
foo: bar
|
||||||
|
something: else
|
||||||
|
`)
|
||||||
|
|
||||||
|
expectedDynamicFieldValue := &dynamicField{
|
||||||
|
A: 1,
|
||||||
|
B: "abcd",
|
||||||
|
C: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"something": "else",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("UseJSONUnmarshaler and json.RawMessage", func(t *testing.T) {
|
||||||
|
var wrapper rawJSONWrapper
|
||||||
|
if err := yaml.UnmarshalWithOptions(rawData, &wrapper, yaml.UseJSONUnmarshaler()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if wrapper.StaticField != "value" {
|
||||||
|
t.Fatalf("unexpected wrapper static field value: %s", wrapper.StaticField)
|
||||||
|
}
|
||||||
|
var dynamicFieldValue dynamicField
|
||||||
|
if err := yaml.Unmarshal(wrapper.DynamicField, &dynamicFieldValue); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !dynamicFieldValue.Equals(expectedDynamicFieldValue) {
|
||||||
|
t.Fatalf("unexpected dynamic field value: %v", dynamicFieldValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UseJSONUnmarshaler and yaml.RawMessage", func(t *testing.T) {
|
||||||
|
var wrapper rawYAMLWrapper
|
||||||
|
if err := yaml.UnmarshalWithOptions(rawData, &wrapper, yaml.UseJSONUnmarshaler()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if wrapper.StaticField != "value" {
|
||||||
|
t.Fatalf("unexpected wrapper static field value: %s", wrapper.StaticField)
|
||||||
|
}
|
||||||
|
var dynamicFieldValue dynamicField
|
||||||
|
if err := yaml.Unmarshal(wrapper.DynamicField, &dynamicFieldValue); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !dynamicFieldValue.Equals(expectedDynamicFieldValue) {
|
||||||
|
t.Fatalf("unexpected dynamic field value: %v", dynamicFieldValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UseJSONMarshaler and json.RawMessage", func(t *testing.T) {
|
||||||
|
dynamicFieldBytes, err := yaml.Marshal(expectedDynamicFieldValue)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wrapper := rawJSONWrapper{
|
||||||
|
StaticField: "value",
|
||||||
|
DynamicField: json.RawMessage(dynamicFieldBytes),
|
||||||
|
}
|
||||||
|
wrapperBytes, err := yaml.MarshalWithOptions(&wrapper, yaml.UseJSONMarshaler())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var unmarshaledWrapper rawJSONWrapper
|
||||||
|
if err := yaml.UnmarshalWithOptions(wrapperBytes, &unmarshaledWrapper, yaml.UseJSONUnmarshaler()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if unmarshaledWrapper.StaticField != wrapper.StaticField {
|
||||||
|
t.Fatalf("unexpected unmarshaled static field value: %s", unmarshaledWrapper.StaticField)
|
||||||
|
}
|
||||||
|
var unmarshaledDynamicFieldValue dynamicField
|
||||||
|
if err := yaml.UnmarshalWithOptions(unmarshaledWrapper.DynamicField, &unmarshaledDynamicFieldValue, yaml.UseJSONUnmarshaler()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !unmarshaledDynamicFieldValue.Equals(expectedDynamicFieldValue) {
|
||||||
|
t.Fatalf("unexpected unmarshaled dynamic field value: %v", unmarshaledDynamicFieldValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UseJSONMarshaler and yaml.RawMessage", func(t *testing.T) {
|
||||||
|
dynamicFieldBytes, err := yaml.Marshal(expectedDynamicFieldValue)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wrapper := rawYAMLWrapper{
|
||||||
|
StaticField: "value",
|
||||||
|
DynamicField: yaml.RawMessage(dynamicFieldBytes),
|
||||||
|
}
|
||||||
|
wrapperBytes, err := yaml.MarshalWithOptions(&wrapper, yaml.UseJSONMarshaler())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var unmarshaledWrapper rawYAMLWrapper
|
||||||
|
if err := yaml.UnmarshalWithOptions(wrapperBytes, &unmarshaledWrapper, yaml.UseJSONUnmarshaler()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if unmarshaledWrapper.StaticField != wrapper.StaticField {
|
||||||
|
t.Fatalf("unexpected unmarshaled static field value: %s", unmarshaledWrapper.StaticField)
|
||||||
|
}
|
||||||
|
var unmarshaledDynamicFieldValue dynamicField
|
||||||
|
if err := yaml.UnmarshalWithOptions(unmarshaledWrapper.DynamicField, &unmarshaledDynamicFieldValue, yaml.UseJSONUnmarshaler()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !unmarshaledDynamicFieldValue.Equals(expectedDynamicFieldValue) {
|
||||||
|
t.Fatalf("unexpected unmarshaled dynamic field value: %v", unmarshaledDynamicFieldValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue