3 ms·
One of the somewhat useful (but still mind-boggling) uses of local types I've encountered was in Go, writing a custom unmarshaller: func (s *MyLovelyStruct
by Joker_vD 2y ago
One of the somewhat useful (but still mind-boggling) uses of local types I've encountered was in Go, writing a custom unmarshaller:
func (s *MyLovelyStruct) UnmarshalJSON(b []byte) error {
err := json.Unmarshal(b, s)
if err != nil {
return err
}
return validateAndMassageMyLovelyStruct(s)
}
I want to re-use the default "dump JSON key/values into the struct's fields" logic, then add something on top of it. But as written, this method will blow up with stack overflow because json.Unmarshal(b, s) will call s.UnmarshalJSON(b), if it can. So what you can do is this:
func (s *MyLovelyStruct) UnmarshalJSON(b []byte) error {
type IncognitoStruct MyLovelyStruct
tmp := IncognitoStruct{}
err := json.Unmarshal(b, &tmp)
if err != nil {
return err
}
*s = MyLovelyStruct(tmp)
return validateAndMassageMyLovelyStruct(s)
}
The IncognitoStruct, even if it has exact same fields as MyLovelyStruct (and is castable to it), does not have any of its methods, so json.Unmarshal(b, &tmp) does not recursively call this UnmarshalJSON() method.
But even that uses type aliases, not the proper, data-holding, types themselves. I never found any motivation to use those; the package-local types are quite enough.
- nkozyra 2y agoWouldn't generics in the validateAndMassageMyLovelyStruct() function avoid this kind of workaround?
- jerf 2y agoNo, because the method infinitely recurses before then.
- jerf 2y agoYeah, that's one in my bag-of-JSON-tricks. (Hopefully my bag-of-JSON-tricks gets at least a little less populated with json v2.) This is also useful if you want to selectively override a particular Unmarshal for any other reason, which is what I needed it for. But then I needed to customize the Unmarshal and back to a top-level type it went. :) But this sort of thing is why I tried to emphasize at the end that I'm not trying to "ring the alarm bell" or anything. When it works, it works, and it's not like I would call what you have there Bad Code or anything. I personally would have pulled it to a top level type immediately and that's just a preference, not something I'd go to bat over in a code review or anything.
- oefrha 2y agoSometimes you just want to JSON-encode a []struct{<ad hoc stuff>} or something like that, so it’s entirely reasonable to use a func-local named type rather than repeating the anonymous struct. And to gp: if your func local type ends up observable and even testable, of course it shouldn’t be func local. Otherwise you’re describing testing implementation rather than behavior, indicating you’re writing bad tests.
- jerf 2y ago"Otherwise you’re describing testing implementation rather than behavior, indicating you’re writing bad tests." Yeah, people have been threatening me for decades with the claim that if I write tests to test internals I'll have to refactor like crazy someday. I'm still waiting for someday to come. Meanwhile, it has caught a lot of bugs. I'm open to the possibility that there's something different about the way I write code that causes me to not have this problem. Stay tuned to my blog over the next couple of months if that intrigues you. In the meantime, as reality fails to correspond to theory, I go with reality.
- masklinn 2y agoI don't know how common it is in the wild, but deserializing to a function-local type is routinely used by Serde's documentation for examples e.g. https://serde.rs/deserialize-struct.html https://serde.rs/deserialize-struct.html