Skip to content

Commit

Permalink
jsonutils test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
guscarreon committed Feb 12, 2024
1 parent 86ee89c commit 84f852b
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 3 deletions.
16 changes: 13 additions & 3 deletions util/jsonutil/jsonutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,23 +214,29 @@ 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{}
}
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)
Expand All @@ -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)) == ""
}
120 changes: 120 additions & 0 deletions util/jsonutil/jsonutil_test.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand Down Expand Up @@ -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))
})
}
}

0 comments on commit 84f852b

Please sign in to comment.