-
Notifications
You must be signed in to change notification settings - Fork 44
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
Enhancement: form.Marshaler
and form.Unmarshaler
#63
base: master
Are you sure you want to change the base?
Enhancement: form.Marshaler
and form.Unmarshaler
#63
Conversation
Looks like |
Thank you for the PR @tigh-latte! I think this would be an amazing addition and will take a deeper look at it this weekend. I already took a cursory look and code looks solid, I am only thinking about the interface and its signature function/method name. |
Sure thing, let me know if you have any better/preferred suggestions. |
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.
Hey @tigh-latte
I was able to find time to more thoroughly review the code and think about the interface names.
- I like the interface names as is :)
- I left a comment to see if changing the unmarshalling logic slightly was possible only to keep the flow of the program using the existing
Ptr
logic.
And again TY for the PR, can't wait to merge this.
encoder.go
Outdated
@@ -102,6 +98,20 @@ func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx in | |||
return | |||
} | |||
} | |||
{ |
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'm not sure the extra scope is necessary.
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.
it is not
encoder.go
Outdated
if t := v.Type(); t.Kind() == reflect.Ptr && v.IsNil() { | ||
return | ||
} |
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.
Is this check necessary?
- Below this logic
reflect.Ptr
is handled and returned. - What is someone wanted to set a default value when it was nil from the custom marshaller, would this prevent that?
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 I've played around with this, and could get something sorted that didn't risk a nil pointer in some form.
For example, by removing this check and making one or two other modifications, I ended up allowing this following to work:
func (c *custom) MarshalForm() ([]string, error) {
if c == nil {
return // some default value
}
return // real handling
}
func main() {
var c *custom
NewEncoder().Encode(c)
}
However, if MarshalForm
is changed to have a value receiver, then we'd end up with a nil panic because the nil *custom
passes the form.Marshaler
implementation check, then have its MarshalForm
function called, causing the panic once dereferenced to the receiver.
All this playing about ended up having me take a read of the json.Marshaler
implementation in the standard library. It's behaviour is basically just, if the item being marshalled is nil, write "null"
(see here, and here).
The way the std lib handles its reflection checks is much better than what I've done here so I'm gonna do a small rewrite, but do let me know what behaviour you would prefer regarding this.
decoder.go
Outdated
@@ -199,6 +194,27 @@ func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx in | |||
} | |||
} | |||
} | |||
{ |
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'm not sure the extra scope is needed.
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.
This was added so that v could be shadowed and modified without changing the code later on, but I'll just assign all modifications to a new var instead.
form_decoder.go
Outdated
@@ -8,6 +8,11 @@ import ( | |||
"sync" | |||
) | |||
|
|||
// Unmarshaler is the interface implemented by an object that can unmarshal a form representation of itself. | |||
type Unmarshaler interface { | |||
UnmarshalForm(ss []string) error |
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.
UnmarshalForm(ss []string) error | |
UnmarshalForm([]string) error |
decoder.go
Outdated
v := v // deliberately shadow v as to not modify | ||
t := v.Type() | ||
if t.Kind() != reflect.Ptr && v.CanAddr() { | ||
v = v.Addr() | ||
} | ||
if um, ok := v.Interface().(Unmarshaler); ok { | ||
// if receiver is a nil pointer, set before calling function. | ||
if t.Kind() == reflect.Ptr && v.IsNil() { | ||
t = t.Elem() | ||
v.Set(reflect.New(t)) | ||
um = v.Interface().(Unmarshaler) | ||
} |
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.
Is there any way to avoid doing this/duplicating the Ptr
logic of creating the new element?
below this same logic exists for Ptr
already https://github.com/go-playground/form/pull/63/files#diff-2a967f84f1af0d52111b8dbfbeadc44bfeb1092dacebb2bc0648a0e57284c04bR226-R230.
It would be great if it's possible to keep the flow of the program using that same logic.
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'll have a look at this and see if anything can be done
Fixes Or Enhances
Add support for custom marshalling and unmarshalling to a struct.
The motivation behind this mainly comes from using generics, when looking to register a custom encoder/decoder, you have to register one for every generic implementation of a struct, so for example:
This isn't really great, so instead with these marshaller interfaces we can simply do:
Make sure that you've checked the boxes below before you submit PR:
@go-playground/admins