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 }