-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use ordered map to guarantee consistency of JSON ordering #50
Conversation
1 similar comment
@@ -793,7 +852,7 @@ func TestMarshal_BooleanPtrMap(t *testing.T) { | |||
expect, err := json.Marshal(toMarshal) | |||
assert.NoError(t, err) | |||
|
|||
assert.Equal(t, string(marshal), string(expect)) | |||
assert.JSONEq(t, string(marshal), string(expect)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as we use a map
as the input the order of the result cannot be guaranteed.
Using JSONEq
will ensure the json is correct (even if unordered)
Looking at the benchmark we have in the repo. On my Macbook Pro M1 (10 cores) I get the following benchmark result on master:
with this branch:
when using github.com/wk8/go-ordered-map/v2 instead:
branch: 46.34% slower Now I guess for those who really need it, this might be acceptable. I wonder though if we should support both cases? FYI this was the patch I used to use wk8/go-ordered-map instead: diff --git a/sheriff.go b/sheriff.go
index 2c8a38e..e28aa18 100644
--- a/sheriff.go
+++ b/sheriff.go
@@ -8,7 +8,7 @@ import (
"strings"
"github.com/hashicorp/go-version"
- "gitlab.com/c0b/go-ordered-json"
+ orderedmap "github.com/wk8/go-ordered-map/v2"
)
// Options determine which struct fields are being added to the output map.
@@ -83,7 +83,7 @@ func Marshal(options *Options, data interface{}) (interface{}, error) {
return marshalValue(options, v)
}
- dest := ordered.NewOrderedMap()
+ dest := orderedmap.New[string, any]()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
@@ -198,14 +198,9 @@ func Marshal(options *Options, data interface{}) (interface{}, error) {
// when a composition field we want to bring the child
// nodes to the top
- nestedVal, ok := v.(*ordered.OrderedMap)
+ nestedVal, ok := v.(*orderedmap.OrderedMap[string, any])
if isEmbeddedField && ok {
- iter := nestedVal.EntriesIter()
- for {
- pair, ok := iter()
- if !ok {
- break
- }
+ for pair := nestedVal.Newest(); pair != nil; pair = pair.Prev() {
dest.Set(pair.Key, pair.Value)
}
} else {
@@ -274,7 +269,7 @@ func marshalValue(options *Options, v reflect.Value) (interface{}, error) {
if mapKeys[0].Kind() != reflect.String {
return nil, MarshalInvalidTypeError{t: mapKeys[0].Kind(), data: val}
}
- dest := ordered.NewOrderedMap()
+ dest := orderedmap.New[string, any]()
for _, key := range mapKeys {
d, err := marshalValue(options, v.MapIndex(key))
if err != nil { what do you think about:
|
|
Sounds great! Thanks 👍 |
One possibility for this would be to define an interface like this (needs better naming!)
The default implementation could be something like this:
Which would be included in this library. You could then use configuration to allow passing in a different implementation. I.e. you could use
This would mean consumers of the library could define their own implementation (documentation could be added with the above example). This would then mean you don't have to add a new dependency to your library, and consumers could build whatever implementation they wanted |
true, that would be also a possibility. Wondering about the UX of it - I guess a README update would be in order to supply this possibility? I like it though. Out of curiosity - what kind of perf impact does this have if you compare current master and the changes with the Map interface (but using the default implementation)? You can run the benchmark using |
Added to a new draft PR here so we can discuss: #52 Performance looks very similar between both. |
closing in favour of #52 |
Uses orderedmap instead of
map[string]interface{}
to maintain ordering of entries in the marshalled JSON.I appreciate there are some disadvantages to this.
orderedmap
As you note then JSON is not designed to be ordered and as such this is maybe a little redundant.
I've opened this as we are looking to use this library to replace the default json.Marshall across a large code base, and refactoring some tests due to the ordering change would be prohibitively difficult!
fixes #49