From 84f852bfc7b9cba930d27511f973257605c7c36a Mon Sep 17 00:00:00 2001 From: guscarreon Date: Mon, 12 Feb 2024 18:38:39 -0500 Subject: [PATCH] jsonutils test cases --- util/jsonutil/jsonutil.go | 16 ++++- util/jsonutil/jsonutil_test.go | 120 +++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go index 9e64a2bd36f..738958e946e 100644 --- a/util/jsonutil/jsonutil.go +++ b/util/jsonutil/jsonutil.go @@ -214,14 +214,14 @@ func isLikelyDetailedErrorMessage(msg string) bool { return !strings.HasPrefix(msg, "request.") } -var jsonRawMessageType = reflect2.TypeOfPtr(&json.RawMessage{}).Elem() - -// RawMessageExtension was created to call json.Compact() on every json.RawMessage field when getting marshalled. +// RawMessageExtension will call json.Compact() on every json.RawMessage field when getting marshalled. // All other types will be marshalled as usual type RawMessageExtension struct { jsoniter.DummyExtension } +// CreateEncoder substitutes the default jsoniter encoder of the json.RawMessage type with ours, that +// calls json.Compact() before writting to the stream func (e *RawMessageExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder { if typ == jsonRawMessageType { return &rawMessageCodec{} @@ -229,8 +229,14 @@ func (e *RawMessageExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncod return nil } +// jsonRawMessageType is declared here so we don't have to call TypeOfPtr(&json.RawMessage{}).Elem() everytime we encode +var jsonRawMessageType = reflect2.TypeOfPtr(&json.RawMessage{}).Elem() + +// rawMessageCodec implements jsoniter.ValEncoder interface so we can overshadow the default json.RawMessage Encode() +// function with our implementation type rawMessageCodec struct{} +// Encode is intended to bahave as de default json.RawMessage Encode() with the addition of the json.Compact() call func (codec *rawMessageCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { if ptr != nil { jsonRawMsg := *(*[]byte)(ptr) @@ -241,6 +247,10 @@ func (codec *rawMessageCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream } } +// IsEmpty is the second method of the jsoniter.ValEncoder interface func (codec *rawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool { + if ptr == nil { + return true + } return *((*string)(ptr)) == "" } diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go index 09fb6727309..3c7922ade15 100644 --- a/util/jsonutil/jsonutil_test.go +++ b/util/jsonutil/jsonutil_test.go @@ -1,10 +1,15 @@ package jsonutil import ( + "bytes" + "encoding/json" "errors" + "fmt" "strings" "testing" + "unsafe" + jsoniter "github.com/json-iterator/go" "github.com/stretchr/testify/assert" ) @@ -240,3 +245,118 @@ func TestTryExtractErrorMessage(t *testing.T) { }) } } + +func TestCreateEncoder(t *testing.T) { + formatted := `{ + "properties": { + "string": "Blanks spaces in between words to not be removed if compacted", + "integer": 5, + "string_array": [ + "string array elem one", + "string array elem two" + ] + } +}` + compacted := `{"properties":{"string":"Blanks spaces in between words to not be removed if compacted","integer":5,"string_array":["string array elem one","string array elem two"]}}` + + type testCase struct { + desc string + test func(t *testing.T) + } + testGroup := []struct { + desc string + tests []testCase + }{ + { + desc: "Extension registered", + tests: []testCase{ + { + desc: "JSON inside a string field is passed to Marshal(), don't expect the output to be compacted", + test: func(t *testing.T) { + jsoniter.RegisterExtension(&RawMessageExtension{}) + out, err := Marshal(formatted) + assert.NoError(t, err) + assert.NotEqual(t, compacted, string(out)) + }, + }, + { + desc: "json.RawMessage is passed to Marshal(), expect inner JSON blob to be line-break-free, tab-free, spaces only found inside strings, and compacted into one line", + test: func(t *testing.T) { + jsoniter.RegisterExtension(&RawMessageExtension{}) + out, err := Marshal(json.RawMessage(formatted)) + assert.NoError(t, err) + assert.Equal(t, compacted, string(out)) + }, + }, + }, + }, + { + desc: "Extension not registered, json.RawMessage won't get compacted", + tests: []testCase{ + { + desc: "json.RawMessage not cleared of line breaks, tabs, nor compacted into one line", + test: func(t *testing.T) { + jsoniter.RegisterExtension(&RawMessageExtension{}) + out, err := Marshal(json.RawMessage(formatted)) + assert.NoError(t, err) + assert.Equal(t, compacted, string(out)) + }, + }, + }, + }, + } + + for _, group := range testGroup { + for _, tc := range group.tests { + t.Run(fmt.Sprintf("%s - %s", group.desc, tc.desc), tc.test) + } + } +} + +func TestEncode(t *testing.T) { + jsonBlob := json.RawMessage(`{ + "properties": { + "string": "Blanks spaces in between words to not be removed if compacted", + "integer": 5, + "string_array": [ + "string array elem one", + "string array elem two" + ] + } +}`) + + testCases := []struct { + desc string + inPtr unsafe.Pointer + expectedBuffer string + expectedIsEmpty bool + }{ + { + desc: "Nil pointer, expect encoder to not write anything to buffer", + inPtr: nil, + expectedIsEmpty: true, + expectedBuffer: "", + }, + { + desc: "json.RawMessage passed, expect encoder to write the corresponding compacted json data", + inPtr: unsafe.Pointer(&jsonBlob), + expectedIsEmpty: false, + expectedBuffer: `{"properties":{"string":"Blanks spaces in between words to not be removed if compacted","integer":5,"string_array":["string array elem one","string array elem two"]}}`, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + // set test + encoder := &rawMessageCodec{} + output := bytes.NewBuffer([]byte{}) + stream := jsoniter.NewStream(jsonConfigValidationOn, output, len(jsonBlob)) + + // run + encoder.Encode(tc.inPtr, stream) + + // assertions + assert.Equal(t, tc.expectedBuffer, output.String()) + assert.Equal(t, tc.expectedIsEmpty, encoder.IsEmpty(tc.inPtr)) + }) + } +}