github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/vault/sdk/helper/jsonutil/json.go (about)

     1  package jsonutil
     2  
     3  import (
     4  	"bytes"
     5  	"compress/gzip"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/hashicorp/errwrap"
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/vault/sdk/helper/compressutil"
    12  )
    13  
    14  // Encodes/Marshals the given object into JSON
    15  func EncodeJSON(in interface{}) ([]byte, error) {
    16  	if in == nil {
    17  		return nil, fmt.Errorf("input for encoding is nil")
    18  	}
    19  	var buf bytes.Buffer
    20  	enc := json.NewEncoder(&buf)
    21  	if err := enc.Encode(in); err != nil {
    22  		return nil, err
    23  	}
    24  	return buf.Bytes(), nil
    25  }
    26  
    27  // EncodeJSONAndCompress encodes the given input into JSON and compresses the
    28  // encoded value (using Gzip format BestCompression level, by default). A
    29  // canary byte is placed at the beginning of the returned bytes for the logic
    30  // in decompression method to identify compressed input.
    31  func EncodeJSONAndCompress(in interface{}, config *compressutil.CompressionConfig) ([]byte, error) {
    32  	if in == nil {
    33  		return nil, fmt.Errorf("input for encoding is nil")
    34  	}
    35  
    36  	// First JSON encode the given input
    37  	encodedBytes, err := EncodeJSON(in)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	if config == nil {
    43  		config = &compressutil.CompressionConfig{
    44  			Type:                 compressutil.CompressionTypeGzip,
    45  			GzipCompressionLevel: gzip.BestCompression,
    46  		}
    47  	}
    48  
    49  	return compressutil.Compress(encodedBytes, config)
    50  }
    51  
    52  // DecodeJSON tries to decompress the given data. The call to decompress, fails
    53  // if the content was not compressed in the first place, which is identified by
    54  // a canary byte before the compressed data. If the data is not compressed, it
    55  // is JSON decoded directly. Otherwise the decompressed data will be JSON
    56  // decoded.
    57  func DecodeJSON(data []byte, out interface{}) error {
    58  	if data == nil || len(data) == 0 {
    59  		return fmt.Errorf("'data' being decoded is nil")
    60  	}
    61  	if out == nil {
    62  		return fmt.Errorf("output parameter 'out' is nil")
    63  	}
    64  
    65  	// Decompress the data if it was compressed in the first place
    66  	decompressedBytes, uncompressed, err := compressutil.Decompress(data)
    67  	if err != nil {
    68  		return errwrap.Wrapf("failed to decompress JSON: {{err}}", err)
    69  	}
    70  	if !uncompressed && (decompressedBytes == nil || len(decompressedBytes) == 0) {
    71  		return fmt.Errorf("decompressed data being decoded is invalid")
    72  	}
    73  
    74  	// If the input supplied failed to contain the compression canary, it
    75  	// will be notified by the compression utility. Decode the decompressed
    76  	// input.
    77  	if !uncompressed {
    78  		data = decompressedBytes
    79  	}
    80  
    81  	return DecodeJSONFromReader(bytes.NewReader(data), out)
    82  }
    83  
    84  // Decodes/Unmarshals the given io.Reader pointing to a JSON, into a desired object
    85  func DecodeJSONFromReader(r io.Reader, out interface{}) error {
    86  	if r == nil {
    87  		return fmt.Errorf("'io.Reader' being decoded is nil")
    88  	}
    89  	if out == nil {
    90  		return fmt.Errorf("output parameter 'out' is nil")
    91  	}
    92  
    93  	dec := json.NewDecoder(r)
    94  
    95  	// While decoding JSON values, interpret the integer values as `json.Number`s instead of `float64`.
    96  	dec.UseNumber()
    97  
    98  	// Since 'out' is an interface representing a pointer, pass it to the decoder without an '&'
    99  	return dec.Decode(out)
   100  }