Files
releases/util/json.go
T
Stephan Renatus 61b8c1463d util/Unmarshal: if it's JSON, skip YAMLtoJSON (#4681)
This helper accepts JSON or YAML, and used to do this:

1. For YAML input, yaml.YAMLToJSON would parse it as yaml, marshal it to
   JSON, and pass that back out to be passed to UnmarshalJSON
2. For JSON input, yaml.YAMLToJSON would also parse it as yaml, marshal it
   to JSON, and pass that back out to UnmarshalJSON

Issue #4673 has shown that the theoretical "superset" propery of YAML doesn't
seem to hold in all cases.

So now, we'll do this:

1. For YAML input, yaml.YAMLToJSON would parse it as yaml, marshal it to
   JSON, and pass that back out to be passed to UnmarshalJSON
2. For JSON input, json.Valid will determine that it's JSON, and we'll
   feed it into UnmarshalJSON as-is.

The YAML path (1.) still seems suboptimal, but I also suspect that JSON is
more common. Also, this change shouldn't make the YAML path much worse:
determining that yaml string isn't valid JSON should be quick.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-05-12 20:30:10 +02:00

117 lines
3.0 KiB
Go

// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package util
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
"github.com/ghodss/yaml"
)
// UnmarshalJSON parses the JSON encoded data and stores the result in the value
// pointed to by x.
//
// This function is intended to be used in place of the standard json.Marshal
// function when json.Number is required.
func UnmarshalJSON(bs []byte, x interface{}) (err error) {
buf := bytes.NewBuffer(bs)
decoder := NewJSONDecoder(buf)
if err := decoder.Decode(x); err != nil {
return err
}
// Since decoder.Decode validates only the first json structure in bytes,
// check if decoder has more bytes to consume to validate whole input bytes.
tok, err := decoder.Token()
if tok != nil {
return fmt.Errorf("error: invalid character '%s' after top-level value", tok)
}
if err != nil && err != io.EOF {
return err
}
return nil
}
// NewJSONDecoder returns a new decoder that reads from r.
//
// This function is intended to be used in place of the standard json.NewDecoder
// when json.Number is required.
func NewJSONDecoder(r io.Reader) *json.Decoder {
decoder := json.NewDecoder(r)
decoder.UseNumber()
return decoder
}
// MustUnmarshalJSON parse the JSON encoded data and returns the result.
//
// If the data cannot be decoded, this function will panic. This function is for
// test purposes.
func MustUnmarshalJSON(bs []byte) interface{} {
var x interface{}
if err := UnmarshalJSON(bs, &x); err != nil {
panic(err)
}
return x
}
// MustMarshalJSON returns the JSON encoding of x
//
// If the data cannot be encoded, this function will panic. This function is for
// test purposes.
func MustMarshalJSON(x interface{}) []byte {
bs, err := json.Marshal(x)
if err != nil {
panic(err)
}
return bs
}
// RoundTrip encodes to JSON, and decodes the result again.
//
// Thereby, it is converting its argument to the representation expected by
// rego.Input and inmem's Write operations. Works with both references and
// values.
func RoundTrip(x *interface{}) error {
bs, err := json.Marshal(x)
if err != nil {
return err
}
return UnmarshalJSON(bs, x)
}
// Reference returns a pointer to its argument unless the argument already is
// a pointer. If the argument is **t, or ***t, etc, it will return *t.
//
// Used for preparing Go types (including pointers to structs) into values to be
// put through util.RoundTrip().
func Reference(x interface{}) *interface{} {
var y interface{}
rv := reflect.ValueOf(x)
if rv.Kind() == reflect.Ptr {
return Reference(rv.Elem().Interface())
}
if rv.Kind() != reflect.Invalid {
y = rv.Interface()
return &y
}
return &x
}
// Unmarshal decodes a YAML or JSON value into the specified type.
func Unmarshal(bs []byte, v interface{}) error {
if json.Valid(bs) {
return UnmarshalJSON(bs, v)
}
bs, err := yaml.YAMLToJSON(bs)
if err != nil {
return err
}
return UnmarshalJSON(bs, v)
}