As I was writing yesterday’s post, a portion of the GoDoc confused me. I’ve now spent over 3 hours with Claude trying to parse the prose grammatically, build test cases, and make general sense of it. I think I finally have… Here’s hoping!
So, yesterday we saw how you can lazy-evaluate some values when using TextHandler. But the proposed solution (pass a fmt.Stringer rather than a literal string) has other, likely uninintended, consequences if you’re using JSONHandler:
Performance considerations
… Avoiding the call to String also preserves the structure of the underlying value. For example JSONHandler emits the components of the parsed URL as a JSON object.
Oops! This means that by passing &r.URL, we get lazy string evaluation for TextHandler, but for JSONHandler, we get:
"url":{"Scheme":"http","Opaque":"","User":null,"Host":"example.com","Path":"","Fragment":"","RawQuery":"","RawPath":"","RawFragment":"","ForceQuery":false,"OmitHost":false}
Meh… that’s probably NOT what you want. So how can you get that lazy-evaluation, but in string format, for JSONHandler?
… If you want to avoid eagerly paying the cost of the String call without causing the handler to potentially inspect the structure of the value, wrap the value in a fmt.Stringer implementation that hides its Marshal methods.
There’s the clue, but it’s opaque—and I believe actually wrong. Let me first explain what I think it’s trying to point us to. To get the lazy-evaluation benefit with JSONHandler, we need to rely on MarshalJSON or MarshalText instead of String. Easily done with a custom type that wraps the url.URL:
type myURL url.URL
func (u *myURL) MarshalJSON() ([]byte, error) {
return json.Marshal((*url.URL)(u).String())
}
Or you could add a MarshalText method, which would work for both TextHandler and JSONHandler equally.
So there’s the full arc, as I believe it’s meant to be understood. You can stop reading now if you want to.
However, that’s not actually what the doc says.
The doc says “… wrap the value in a fmt.Stringer implementation that hides its Marshal methods.” — The solution we just came up with adds a Marshal method, it doesn’t hide any. And in fact, the url.URL example in the doc doesn’t have marshal methods (if it did, we wouldn’t need this remedy!)
So, I believe the doc simply has a small error, and means to say:
“… wrap the value with an implementation that adds Marshal methods.”
And that’s where I got hung up while trying to make sense of this. Maybe the doc is actually trying to say something else? Maybe it’s talking about data hiding? Maybe you want a custom fmt.Stringer that omits api_key query parameters for example? But that seems to be a stretch for what appears to be an aside in a section about performance—and doubly so, since the prescription points to fmt.Stringer, which is what introduced the problem it’s meant to be solving for JSONHandler. Which is why I think it’s just worded in a confusing/incorrect way.
How do you read it? Is there an interpretation I’ve overlooked?