github.com/hashicorp/vault/sdk@v0.11.0/helper/jsonutil/json.go (about)

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