-
Notifications
You must be signed in to change notification settings - Fork 601
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
Add json (un)marshalling methods on NullUUID #44
Conversation
@blastrock could you please reimplement it with |
I'm new to go, so maybe I got something wrong, but if I implement MarshalText like this: func (u NullUUID) MarshalText() ([]byte, error) {
if u.Valid == false {
return []byte("null"), nil
}
return []byte(u.UUID.String()), nil
} A null UUID is marshalled in JSON as a string "null" instead of a null value. If I return nil instead of []byte("null"), then it gets marshalled as an empty string "". |
@blastrock Documentation of
The second sentence explains the behavior you're seeing. |
Indeed, but this is not the behavior one would expect, as a null UUID is better represented by a null JSON value than an empty string. In my use-case at least, I need a null JSON value when a NullUUID is not Valid, and I think only MarshalJSON can provide me with this behavior. |
Yes, I agree. I don't think you'll be able to use just |
uuid.go
Outdated
u.Valid = false | ||
} | ||
return nil | ||
} |
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.
Would something like that make UnmarshalJSON a bit cleaner?
func (u *NullUUID) UnmarshalJSON(b []byte) error {
if bytes.Equal(b, []byte("null")) {
return nil
}
if err := json.Unmarshal(b, &u.UUID); err != nil {
return err
}
u.Valid = true
return nil
}
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.
I highly prefer being explicit about return values, I think it's much more readable to see return err
or return nil
than just seeing return
and trying to infer what's being returned.
According to Go style:
Naked returns are okay if the function is a handful of lines. Once it's a medium sized function, be explicit with your return values.
I'd say this is a medium sized function.
Also:
Corollary: it's not worth it to name result parameters just because it enables you to use naked returns. Clarity of docs is always more important than saving a line or two in your function.
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.
@shurcooL thanks for your valuable comment, I've edited my code quirk
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.
So, you removed u.Valid = false
part. I thought it would be better to explicitly initialize all the fields and not rely on default initialization. I don't mind changing that if you want.
As for the comparison with "null", it injects json logic in the function and that should be the responsibility of the json package. Maybe the contents of the buffer is not "null"
but " null "
and that comparison would fail, but the json package will parse it correctly.
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.
So, you removed
u.Valid = false
part. I thought it would be better to explicitly initialize all the fields and not rely on default initialization. I don't mind changing that if you want.
I think you need to explicitly initialize all fields, you cannot rely on default initialization. The value being unmarshaled into may be zero, or it may not be zero. Removing u.Valid = false
would make it fail to operate correctly on &NullUUID{Valid: true}
starting value.
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.
@blastrock JSON decoder tokenizes input and it will pass "null"
to UnmarshalJSON
in any way.
If you look closer besides stylistic changes I removed intermediate UUID allocation by passing &u.UUID
to json.Unmarshal
.
Simple benchmark of original UnmarshalJSON
and patched one:
BenchmarkUnmarshalJSON-4 2000000 879 ns/op 312 B/op 3 allocs/op
vs
BenchmarkUnmarshalJSON-4 2000000 770 ns/op 288 B/op 1 allocs/op
Benchmark itself:
func BenchmarkUnmarshalJSON(b *testing.B) {
bytes := []byte("\"886313e1-3b8a-5372-9b90-0c9aee199e5d\"")
u := NullUUID{}
for i := 0; i < b.N; i++ {
u.UnmarshalJSON(bytes)
}
}
Just for comparison UnmarshalText
benchmark does 0 allocations and 10x faster:
BenchmarkUnmarshalText-4 20000000 88.9 ns/op 0 B/op 0 allocs/op
I agree with @shurcooL's comment about initializing all fields but it might be Scan
method does it the most correct way by initializing UUID
struct member to be Nil
too.
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.
@blastrock JSON decoder tokenizes input and it will pass "null" to UnmarshalJSON in any way.
Indeed, it seems so. But then u.UnmarshalJson(" null ")
wouldn't work, but it may be acceptable. Just tell me your decision on that and I'll update the PR.
If you look closer besides stylistic changes I removed intermediate UUID allocation by passing &u.UUID to json.Unmarshal.
Right, I didn't notice that, and I don't see any other way to remove that allocation.
I agree with @shurcooL's comment about initializing all fields but it might be Scan method does it the most correct way by initializing UUID struct member to be Nil too.
Will do.
uuid.go
Outdated
return nil | ||
} | ||
|
||
u.Valid = true |
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.
Wouldn't it make more sense to do u.Valid = true
after if err := json.Unmarshal(b, &u.UUID); err != nil { ... }
? What's the reason you decided to put it on top, instead of below?
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.
You are right, putting it after the if makes more sense.
1 similar comment
This seems like a good change. Any updates here? |
I am having the same problem, I think this PR is good too. |
I have created this small package as a workaround for now. I hope this PR can be merged so I can use satori/go.uuid directly. |
👍 The latest commits work for me in conjunction with go-pg. NullUUID has json marshalled using the json encoder, as appropriate. Here's the quick links to MarshallJSON and UnmarshallJSON. |
Any updates? When this PR will be merged? |
@blastrock Some of us in the Go community have forked this repository with the intent of maintaining it long-term. It seems like this proposed change could be a good addition to the new fork. Would you be interested in raising this change against our repo? |
|
||
// UnmarshalJSON unmarshalls a NullUUID | ||
func (u *NullUUID) UnmarshalJSON(b []byte) error { | ||
if bytes.Equal(b, []byte("null")) { |
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.
Should we mark this as an invalid UUID? It seems like sending null
could be a very explicit way to do the same thing, without sending 16 NULL bytes.
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.
I am not sure what you mean here, we must fill the NullUUID struct. Unless we change it to contain a pointer, we can't set u.UUID to nil, can we?
@theckman thanks for forking and maintaining this package! I'll do that when I have some time. To tell the truth, we are not using this package anymore in our project. Of course, I wouldn't mind if you just take this code and put it in your fork, and I don't care about getting my name in the commit either ^^ |
This commit adapts satori/go.uuid#44 for our fork of the original project. This brings JSON Marshaling and Unmarshaling to the `uuid.NullUUID` type used for use with SQL databases. This is needed because `uuid.NullUUID` is a shim around `uuid.UUID`, providing the information needed by the `database/sql` package to support storing the value in a nullable column. Without these methods, the type gets converted unnecessarily to a different format than the standard `uuid.UUID` type. Signed-off-by: Tim Heckman <t@heckman.io>
@blastrock I took a swing adapting this PR for our fork, and was able to keep a commit attributing it to you. gofrs/uuid#38 |
This commit adapts satori/go.uuid#44 for our fork of the original project. This brings JSON Marshaling and Unmarshaling to the `uuid.NullUUID` type used for use with SQL databases. This is needed because `uuid.NullUUID` is a shim around `uuid.UUID`, providing the information needed by the `database/sql` package to support storing the value in a nullable column. Without these methods, the type gets converted unnecessarily to a different format than the standard `uuid.UUID` type. Signed-off-by: Tim Heckman <t@heckman.io>
This commit adapts satori/go.uuid#44 for our fork of the original project. This brings JSON Marshaling and Unmarshaling to the `uuid.NullUUID` type used for use with SQL databases. This is needed because `uuid.NullUUID` is a shim around `uuid.UUID`, providing the information needed by the `database/sql` package to support storing the value in a nullable column. Without these methods, the type gets converted unnecessarily to a different format than the standard `uuid.UUID` type. Signed-off-by: Tim Heckman <t@heckman.io>
This commit adapts satori/go.uuid#44 for our fork of the original project. This brings JSON Marshaling and Unmarshaling to the `uuid.NullUUID` type used for use with SQL databases. This is needed because `uuid.NullUUID` is a shim around `uuid.UUID`, providing the information needed by the `database/sql` package to support storing the value in a nullable column. Without these methods, the type gets converted unnecessarily to a different format than the standard `uuid.UUID` type. Signed-off-by: Tim Heckman <t@heckman.io>
This commit adapts satori/go.uuid#44 for our fork of the original project. This brings JSON Marshaling and Unmarshaling to the `uuid.NullUUID` type used for use with SQL databases. This is needed because `uuid.NullUUID` is a shim around `uuid.UUID`, providing the information needed by the `database/sql` package to support storing the value in a nullable column. Without these methods, the type gets converted unnecessarily to a different format than the standard `uuid.UUID` type. Signed-off-by: Tim Heckman <t@heckman.io>
This commit adapts satori/go.uuid#44 for our fork of the original project. This brings JSON Marshaling and Unmarshaling to the `uuid.NullUUID` type used for use with SQL databases. This is needed because `uuid.NullUUID` is a shim around `uuid.UUID`, providing the information needed by the `database/sql` package to support storing the value in a nullable column. Without these methods, the type gets converted unnecessarily to a different format than the standard `uuid.UUID` type. Signed-off-by: Tim Heckman <t@heckman.io>
Great. I'm a bit late but I just reviewed it, seems good to me :) Thank you! |
Without these methods, NullUUID is marshalled as an object with an UUID and a Valid properties. This allows to marshal it as null or a string.