Skip to content

Commit

Permalink
Merge pull request #1117 from sargas/deterministic-json-serialization
Browse files Browse the repository at this point in the history
Sort extensions during JSON serialization
  • Loading branch information
embano1 authored Feb 2, 2025
2 parents 46808b5 + 618ca71 commit 68fe9b5
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 6 deletions.
13 changes: 10 additions & 3 deletions v2/event/event_marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"encoding/base64"
"fmt"
"io"
"slices"
"strings"

jsoniter "github.com/json-iterator/go"
Expand Down Expand Up @@ -179,10 +180,16 @@ func WriteJson(in *Event, writer io.Writer) error {
return fmt.Errorf("error while writing the event data: %w", stream.Error)
}

for k, v := range ext {
// Add extensions in a deterministic predictable order, similar to how Go maps are serialized in a predictable order.
extensionNames := make([]string, 0, len(ext))
for extName := range ext {
extensionNames = append(extensionNames, extName)
}
slices.Sort(extensionNames)
for _, extName := range extensionNames {
stream.WriteMore()
stream.WriteObjectField(k)
stream.WriteVal(v)
stream.WriteObjectField(extName)
stream.WriteVal(ext[extName])
}

stream.WriteObjectEnd()
Expand Down
35 changes: 32 additions & 3 deletions v2/event/event_marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
package event_test

import (
"bytes"
"encoding/base64"
"encoding/json"
"net/url"
"slices"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -384,20 +387,21 @@ func TestMarshal(t *testing.T) {
}
for n, tc := range testCases {
t.Run(n, func(t *testing.T) {
event := tc.event
testEvent := tc.event

for k, v := range tc.eventExtensions {
event.SetExtension(k, v)
testEvent.SetExtension(k, v)
}

gotBytes, err := json.Marshal(event)
gotBytes, err := json.Marshal(testEvent)

if tc.wantErr != nil {
require.Error(t, err, *tc.wantErr)
return
}

assertJsonEquals(t, tc.want, gotBytes)
assertExtensionsAreOrdered(t, tc.eventExtensions, gotBytes)
})
}
}
Expand All @@ -420,3 +424,28 @@ func assertJsonEquals(t *testing.T, want map[string]interface{}, got []byte) {

require.Equal(t, wantToCompare, gotToCompare)
}

func assertExtensionsAreOrdered(t *testing.T, extensions map[string]any, got []byte) {
type extensionLocation struct {
extension string
idx int
}
extensionLocations := make([]extensionLocation, 0, len(extensions))
for ext := range extensions {
extensionLocations = append(extensionLocations, extensionLocation{
extension: ext,
idx: bytes.Index(got, []byte(ext)),
})
}

// Sort by extension name
slices.SortFunc(extensionLocations, func(i, j extensionLocation) int {
return strings.Compare(i.extension, j.extension)
})
indexes := make([]int, 0, len(extensions))
for ext := range extensionLocations {
indexes = append(indexes, extensionLocations[ext].idx)
}

require.IsIncreasingf(t, indexes, "Expected ordered extensions, found extensions at: %+v", extensionLocations)
}

0 comments on commit 68fe9b5

Please sign in to comment.