Skip to content

Commit

Permalink
use buffer to improve JsonSerializationWriter performance (#79)
Browse files Browse the repository at this point in the history
* use buffer to improve serializer performance

* code review fixes

* Update CHANGELOG.md

---------

Co-authored-by: Vincent Biret <vincentbiret@hotmail.com>
  • Loading branch information
eric-millin and baywet committed Apr 24, 2023
1 parent b75d6fe commit 7e30e41
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 31 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added


## [0.9.2] - 2023-04-17

### Changed

- Improve `JsonSerializationWriter` serialization performance.

## [0.9.1] - 2023-04-05

### Added
Expand Down
62 changes: 33 additions & 29 deletions json_serialization_writer.go
@@ -1,6 +1,7 @@
package jsonserialization

import (
"bytes"
"encoding/base64"
"encoding/json"
"strconv"
Expand All @@ -15,7 +16,8 @@ import (

// JsonSerializationWriter implements SerializationWriter for JSON.
type JsonSerializationWriter struct {
writer []string
writer *bytes.Buffer
separatorIndices []int
onBeforeAssignFieldValues absser.ParsableAction
onAfterAssignFieldValues absser.ParsableAction
onStartObjectSerialization absser.ParsableWriter
Expand All @@ -24,40 +26,31 @@ type JsonSerializationWriter struct {
// NewJsonSerializationWriter creates a new instance of the JsonSerializationWriter.
func NewJsonSerializationWriter() *JsonSerializationWriter {
return &JsonSerializationWriter{
writer: make([]string, 0),
writer: new(bytes.Buffer),
separatorIndices: make([]int, 0),
}
}
func (w *JsonSerializationWriter) writeRawValue(value string) {
w.writer = append(w.writer, value)
func (w *JsonSerializationWriter) writeRawValue(value ...string) {
for _, v := range value {
w.writer.WriteString(v)
}
}
func (w *JsonSerializationWriter) writeStringValue(value string) {
value = strings.ReplaceAll(value, `\`, `\\`)
value = strings.ReplaceAll(value, `"`, `\"`)
value = strings.ReplaceAll(value, "\n", `\n`)
value = strings.ReplaceAll(value, "\r", `\r`)
value = strings.ReplaceAll(value, "\t", `\t`)

value = strings.ReplaceAll(
strings.ReplaceAll(
strings.ReplaceAll(value,
"\\", "\\\\",
),
"\"",
"\\\""),
"\n",
"\\n")
value = strings.ReplaceAll(strings.ReplaceAll(value, "\t", "\\t"), "\r", "\\r")
w.writeRawValue("\"" + value + "\"")
w.writeRawValue("\"", value, "\"")
}
func (w *JsonSerializationWriter) writePropertyName(key string) {
w.writeRawValue("\"" + key + "\":")
w.writeRawValue("\"", key, "\":")
}
func (w *JsonSerializationWriter) writePropertySeparator() {
w.separatorIndices = append(w.separatorIndices, w.writer.Len())
w.writeRawValue(",")
}
func (w *JsonSerializationWriter) trimLastPropertySeparator() {
for idx, s := range w.writer {
writerLen := len(w.writer)
if s == "," && (idx == writerLen-1 || (idx < writerLen-1 && (w.writer[idx+1] == "]" || w.writer[idx+1] == "}" || w.writer[idx+1] == ","))) {
w.writer[idx] = ""
}
}
}
func (w *JsonSerializationWriter) writeArrayStart() {
w.writeRawValue("[")
}
Expand Down Expand Up @@ -614,14 +607,24 @@ func (w *JsonSerializationWriter) WriteCollectionOfInt8Values(key string, collec

// GetSerializedContent returns the resulting byte array from the serialization writer.
func (w *JsonSerializationWriter) GetSerializedContent() ([]byte, error) {
w.trimLastPropertySeparator()
resultStr := strings.Join(w.writer, "")
return []byte(resultStr), nil
trimmed := w.writer.Bytes()
buffLen := len(trimmed)

for i := len(w.separatorIndices) - 1; i >= 0; i-- {
idx := w.separatorIndices[i]

if idx == buffLen-1 {
trimmed = trimmed[0:idx]
} else if trimmed[idx+1] == byte(']') || trimmed[idx+1] == byte('}') {
trimmed = append(trimmed[0:idx], trimmed[idx+1:]...)
}
}

return trimmed, nil
}

// WriteAnyValue an unknown value as a parameter.
func (w *JsonSerializationWriter) WriteAnyValue(key string, value interface{}) error {

if value != nil {
body, err := json.Marshal(value)
if err != nil {
Expand Down Expand Up @@ -779,6 +782,7 @@ func (w *JsonSerializationWriter) WriteAdditionalData(value map[string]interface

// Close clears the internal buffer.
func (w *JsonSerializationWriter) Close() error {
w.writer = make([]string, 0)
w.writer = new(bytes.Buffer)
w.separatorIndices = make([]int, 0)
return nil
}
21 changes: 19 additions & 2 deletions json_serialization_writer_test.go
Expand Up @@ -3,10 +3,11 @@ package jsonserialization
import (
"encoding/json"
"fmt"
"github.com/microsoft/kiota-serialization-json-go/internal"
"testing"
"time"

"github.com/microsoft/kiota-serialization-json-go/internal"

assert "github.com/stretchr/testify/assert"

absser "github.com/microsoft/kiota-abstractions-go/serialization"
Expand Down Expand Up @@ -108,7 +109,7 @@ func TestWriteByteValue(t *testing.T) {
assert.Equal(t, fmt.Sprintf("\"key\":%d", value), string(result[:]))
}

// ByteArray values are encoded to Base64 when stored
// ByteArray values are encoded to Base64 when stored
func TestWriteByteArrayValue(t *testing.T) {
serializer := NewJsonSerializationWriter()
value := []byte("SerialWriter")
Expand Down Expand Up @@ -193,6 +194,7 @@ func TestWriteInvalidAdditionalData(t *testing.T) {
err := serializer.WriteAdditionalData(adlData)
assert.Nil(t, err)
result, err := serializer.GetSerializedContent()
assert.NoError(t, err)

stringResult := string(result[:])
assert.Contains(t, stringResult, "\"pointer_node\":")
Expand Down Expand Up @@ -222,6 +224,21 @@ func TestEscapesNewLinesInStrings(t *testing.T) {
assert.Equal(t, value, parsedResult.Key)
}

func TestPreserveSeparatorsInStrings(t *testing.T) {
serializer := NewJsonSerializationWriter()
value := `{"foo":"bar","biz":"bang"},[1,2,3,],,`
serializer.WriteStringValue("key", &value)
result, err := serializer.GetSerializedContent()
assert.Nil(t, err)
assert.Equal(t, `"key":"{\"foo\":\"bar\",\"biz\":\"bang\"},[1,2,3,],,"`, string(result))

fullPayload := "{" + string(result[:]) + "}"
var parsedResult TestStruct
parseErr := json.Unmarshal([]byte(fullPayload), &parsedResult)
assert.Nil(t, parseErr)
assert.Equal(t, value, parsedResult.Key)
}

func TestEscapesBackslashesInStrings(t *testing.T) {
serializer := NewJsonSerializationWriter()
value := "value\\with\\backslashes"
Expand Down

0 comments on commit 7e30e41

Please sign in to comment.